Skip to content

Commit 3c4d410

Browse files
MichaelChiricohcirellu
authored andcommitted
Fix ordertab() segfault when nunique is incorrectly supplied (r-lib#207)
* Fix: ordertab() segfaults when nunique is smaller than actual The ordertab() C function was susceptible to a buffer overflow when the 'nunique' parameter passed from R was smaller than the actual number of unique values in the data. This led to intermittent segfaults due to out-of-bounds writes to the 'ret' array. This commit introduces a bounds check in the C implementation of ordertab() (r_ram_integer64_ordertab_asc). If the number of unique values encountered exceeds the allocated size of the 'ret' vector, the loop is now terminated early, preventing any out-of-bounds writes and thus eliminating the segfault. The function will now return a truncated result in such cases, consistent with R's usual handling of mismatched lengths. A new test case has been added to test-sortuse64.R to specifically target this scenario, using gctorture() to reliably trigger the condition. This test now passes, confirming the fix. Updated NEWS.md to reflect this bug fix. * To taste * tighten test * comment purpose * citation
1 parent d1ce776 commit 3c4d410

3 files changed

Lines changed: 19 additions & 0 deletions

File tree

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
1. `log(integer64(), base=integer64(1))` no longer warns, consistent with `log(integer(), base=integer())` (#93).
5454
1. `sortfin(integer64(), 1:10)` no longer segfaults (#164).
5555
1. `orderfin(as.integer64(10:1), 1:3, 8:11)` enforces that `table` be sorted by `order` instead of segfaulting (#166).
56+
1. `ordertab()` no longer segfaults when `nunique` is smaller than the actual number of unique values (#168).
5657

5758
## NOTES
5859

src/sortuse64.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,17 +775,21 @@ SEXP r_ram_integer64_ordertab_asc(
775775
ret[pos++] = ret[i];
776776
SET_LENGTH(ret_, pos); /* re-allocates ret_ */
777777
}else{
778+
int n_ret = LENGTH(ret_); // allow bailing if user mis-specified nunique (#168)
779+
if (n_ret > 0) {
778780
j = 0;
779781
ret[j] = 1;
780782
pos = index[j]-1;
781783
for(i=1;i<n;i++){
782784
if (table[index[i]-1]!=table[pos]){
783785
pos = index[i]-1;
786+
if (j + 1 >= n_ret) break;
784787
ret[++j] = 1;
785788
}else{
786789
ret[j]++;
787790
}
788791
}
792+
}
789793
}
790794
PROTECT(ret_); /* Thanks to Tomas Kalibera */
791795
R_Busy(0);

tests/testthat/test-sortuse64.R

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,17 @@ test_that("ordertab and orderdup work", {
6565
expect_identical(orderdup(x, idx, method=2L), rep(c(FALSE, TRUE), c(10L, 14L)))
6666
})
6767

68+
test_that("ordertab handles nunique smaller than actual", {
69+
x = as.integer64(1:10)
70+
x = c(x, x[1:8], x[1:6])
71+
o = order(x)
72+
73+
# makes operation quite slow, but in my test, this segfaulted on the first invocation every time.
74+
# do this inside 'local' because doing so inside 'expect_identical' causes gctorture to apply
75+
# to _every_ allocation induced by testthat as well as rep(). This isolated form is much faster.
76+
res = local({
77+
on.exit(gctorture(FALSE)); gctorture(TRUE)
78+
ordertab(x, o, 4L)
79+
})
80+
expect_identical(res, rep(3L, 4L))
81+
})

0 commit comments

Comments
 (0)