Skip to content

Commit aa81a68

Browse files
authored
Merge pull request #2320 from svaarala/fix-string-endswith-memcmp
Fix memcmp() pointer overflow in string builtin
2 parents 51e49c7 + 334612f commit aa81a68

File tree

3 files changed

+54
-19
lines changed

3 files changed

+54
-19
lines changed

releases/releases.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,3 +1364,4 @@ duktape_releases:
13641364
- "Improve DUK_USE_OS_STRING for macOS, iOS, watchOS, and tvOS (GH-2288)"
13651365
- "Fix nested error handling bug for out-of-memory during error creation (GH-2278, GH-2290)"
13661366
- "Fix failing assert for CBOR.encode() when argument is a zero-size dynamic plain array (GH-2316, GH-2318)"
1367+
- "Fix pointer overflow in String.prototype.startsWith/endsWith() with certain arguments (GH-2320)"

src-input/duk_bi_string.c

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,44 +1493,65 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_locale_compare(duk_hthread *thr)
14931493
#if defined(DUK_USE_ES6)
14941494
DUK_INTERNAL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_hthread *thr) {
14951495
duk_int_t magic;
1496-
duk_hstring *h;
1496+
duk_hstring *h_target;
1497+
duk_size_t blen_target;
14971498
duk_hstring *h_search;
14981499
duk_size_t blen_search;
1499-
const duk_uint8_t *p_cmp_start;
1500-
duk_bool_t result;
1500+
duk_int_t off;
1501+
duk_bool_t result = 0;
1502+
duk_size_t blen_left;
15011503

1502-
h = duk_push_this_coercible_to_string(thr);
1503-
DUK_ASSERT(h != NULL);
1504+
/* Because string byte lengths are in [0,DUK_INT_MAX] it's safe to
1505+
* subtract two string lengths without overflow.
1506+
*/
1507+
DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= DUK_INT_MAX);
1508+
1509+
h_target = duk_push_this_coercible_to_string(thr);
1510+
DUK_ASSERT(h_target != NULL);
15041511

15051512
h_search = duk__str_tostring_notregexp(thr, 0);
15061513
DUK_ASSERT(h_search != NULL);
15071514

15081515
magic = duk_get_current_magic(thr);
15091516

1510-
p_cmp_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h);
1517+
/* Careful to avoid pointer overflows in the matching logic. */
1518+
1519+
blen_target = DUK_HSTRING_GET_BYTELEN(h_target);
15111520
blen_search = DUK_HSTRING_GET_BYTELEN(h_search);
15121521

1522+
#if 0
1523+
/* If search string is longer than the target string, we can
1524+
* never match. Could check explicitly, but should be handled
1525+
* correctly below.
1526+
*/
1527+
if (blen_search > blen_target) {
1528+
goto finish;
1529+
}
1530+
#endif
1531+
1532+
off = 0;
15131533
if (duk_is_undefined(thr, 1)) {
15141534
if (magic) {
1515-
p_cmp_start = p_cmp_start + DUK_HSTRING_GET_BYTELEN(h) - blen_search;
1535+
off = (duk_int_t) blen_target - (duk_int_t) blen_search;
15161536
} else {
1517-
/* p_cmp_start already OK */
1537+
DUK_ASSERT(off == 0);
15181538
}
15191539
} else {
15201540
duk_int_t len;
15211541
duk_int_t pos;
15221542

15231543
DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= DUK_INT_MAX);
1524-
len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h);
1544+
len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h_target);
15251545
pos = duk_to_int_clamped(thr, 1, 0, len);
15261546
DUK_ASSERT(pos >= 0 && pos <= len);
15271547

1548+
off = (duk_int_t) duk_heap_strcache_offset_char2byte(thr, h_target, (duk_uint_fast32_t) pos);
15281549
if (magic) {
1529-
p_cmp_start -= blen_search; /* Conceptually subtracted last, but do already here. */
1550+
off -= (duk_int_t) blen_search;
15301551
}
1531-
DUK_ASSERT(pos >= 0 && pos <= len);
1532-
1533-
p_cmp_start += duk_heap_strcache_offset_char2byte(thr, h, (duk_uint_fast32_t) pos);
1552+
}
1553+
if (off < 0 || off > (duk_int_t) blen_target) {
1554+
goto finish;
15341555
}
15351556

15361557
/* The main comparison can be done using a memcmp() rather than
@@ -1540,16 +1561,18 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_hthread *
15401561
* comparison range.
15411562
*/
15421563

1543-
result = 0;
1544-
if (p_cmp_start >= DUK_HSTRING_GET_DATA(h) &&
1545-
(duk_size_t) (p_cmp_start - (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h)) + blen_search <= DUK_HSTRING_GET_BYTELEN(h)) {
1546-
if (duk_memcmp((const void *) p_cmp_start,
1547-
(const void *) DUK_HSTRING_GET_DATA(h_search),
1548-
(size_t) blen_search) == 0) {
1564+
DUK_ASSERT(off >= 0);
1565+
DUK_ASSERT((duk_size_t) off <= blen_target);
1566+
blen_left = blen_target - (duk_size_t) off;
1567+
if (blen_left >= blen_search) {
1568+
const duk_uint8_t *p_cmp_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_target) + off;
1569+
const duk_uint8_t *p_search = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_search);
1570+
if (duk_memcmp_unsafe((const void *) p_cmp_start, (const void *) p_search, (size_t) blen_search) == 0) {
15491571
result = 1;
15501572
}
15511573
}
15521574

1575+
finish:
15531576
duk_push_boolean(thr, result);
15541577
return 1;
15551578
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*===
2+
done
3+
===*/
4+
5+
try {
6+
var v3 = "c".repeat(536870912);
7+
var v4 = "base64".endsWith(v3);
8+
} catch (e) {
9+
print(e.stack || e);
10+
}
11+
print('done');

0 commit comments

Comments
 (0)