Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# monolix2rx 0.0.7

* Fix `int col` overflow in `getLine` (`src/parseSyntaxErrors.h`). When
reporting a syntax error, `getLine` walks the source string to locate
the offending line. The column accumulator was a signed `int` that
could wrap on lines wider than `INT_MAX` bytes. After the wrap,
`R_Calloc(col + 1, char)` received a tiny (or negative) size, and the
subsequent `memcpy(buf, src + i, col)` then wrote past the
allocation. The fix uses `size_t` for the accumulator and adds
explicit bounds checks before the cast back to `int`.

# monolix2rx 0.0.6

* Updated to add types for rstudio completion
Expand Down
16 changes: 12 additions & 4 deletions src/parseSyntaxErrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@
#define syntaxErrorExtra monolix2rx_syntaxErrorExtra

static inline char *getLine (char *src, int line, int *lloc) {
int cur = 1, col=0, i;
int cur = 1, i;
size_t col = 0;
for(i = 0; src[i] != '\0' && cur != line; i++){
if(src[i] == '\n') cur++;
}
for(col = 0; src[i + col] != '\n' && src[i + col] != '\0'; col++);
*lloc=i+col;
char *buf = R_Calloc(col + 1, char);
for(col = 0; src[i + col] != '\n' && src[i + col] != '\0'; col++){
if (col == (size_t)INT_MAX) {
Rf_error(_("line too long in getLine"));
}
}
if ((size_t)i + col > (size_t)INT_MAX) {
Rf_error(_("source offset overflow in getLine"));
}
*lloc = i + (int)col;
char *buf = R_Calloc((int)col + 1, char);
memcpy(buf, src + i, col);
buf[col] = '\0';
return buf;
Expand Down
40 changes: 40 additions & 0 deletions tests/testthat/test-mem-getline-col-overflow.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
test_that("getLine handles normal-sized lines without error", {
# Sanity check: regular syntax errors still report cleanly after the
# `int col` -> `size_t col` change in getLine.
result <- tryCatch(
.Call(`_monolix2rx_trans_equation`,
"[LONGITUDINAL] EQUATION:\nf = !!!syntax error here!!!\n",
"[LONGITUDINAL] EQUATION:"),
error = function(e) conditionMessage(e),
warning = function(w) conditionMessage(w)
)
# Either an R error/warning is raised or a value is returned;
# specifically, the new "line too long in getLine" must NOT appear.
expect_false(grepl("line too long in getLine|source offset overflow", as.character(result)))
})

test_that("getLine col overflow guard triggers on >INT_MAX-byte line (skipped: requires ~2GB RAM)", {
skip("Requires ~2GB free RAM to construct an >INT_MAX-byte single-line input")
# --- What this test checks ---
# `getLine` (src/parseSyntaxErrors.h) walks `src` to find the column of
# a syntax error. The column accumulator was `int col` and the
# subsequent allocation was `R_Calloc(col + 1, char)`. When the line
# is wider than INT_MAX bytes, `col` wraps to a negative int; the
# allocation either fails or is undersized, and the following
# `memcpy(buf, src + i, col)` writes past the buffer.
#
# The fix changes `col` to `size_t` and bounds-checks before the cast
# back to `int` for the R_Calloc call.
#
# --- How to trigger manually ---
# Construct a single-line input that is wider than INT_MAX bytes and
# ends with a syntax error, so that getLine is actually exercised on
# the giant line.

giant_line <- strrep("x_var_abc", 200000000L) # ~1.8 GB on a single line
bad <- paste0(giant_line, " = !!!bad")
expect_error(
.Call(`_monolix2rx_trans_equation`, bad, "[LONGITUDINAL]"),
"line too long in getLine|source offset overflow"
)
})
Loading