Skip to content

Commit

Permalink
Merge pull request #2320 from svaarala/fix-string-endswith-memcmp
Browse files Browse the repository at this point in the history
Fix memcmp() pointer overflow in string builtin
  • Loading branch information
svaarala authored Jun 13, 2020
2 parents 51e49c7 + 334612f commit aa81a68
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 19 deletions.
1 change: 1 addition & 0 deletions releases/releases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1364,3 +1364,4 @@ duktape_releases:
- "Improve DUK_USE_OS_STRING for macOS, iOS, watchOS, and tvOS (GH-2288)"
- "Fix nested error handling bug for out-of-memory during error creation (GH-2278, GH-2290)"
- "Fix failing assert for CBOR.encode() when argument is a zero-size dynamic plain array (GH-2316, GH-2318)"
- "Fix pointer overflow in String.prototype.startsWith/endsWith() with certain arguments (GH-2320)"
61 changes: 42 additions & 19 deletions src-input/duk_bi_string.c
Original file line number Diff line number Diff line change
Expand Up @@ -1493,44 +1493,65 @@ DUK_INTERNAL duk_ret_t duk_bi_string_prototype_locale_compare(duk_hthread *thr)
#if defined(DUK_USE_ES6)
DUK_INTERNAL duk_ret_t duk_bi_string_prototype_startswith_endswith(duk_hthread *thr) {
duk_int_t magic;
duk_hstring *h;
duk_hstring *h_target;
duk_size_t blen_target;
duk_hstring *h_search;
duk_size_t blen_search;
const duk_uint8_t *p_cmp_start;
duk_bool_t result;
duk_int_t off;
duk_bool_t result = 0;
duk_size_t blen_left;

h = duk_push_this_coercible_to_string(thr);
DUK_ASSERT(h != NULL);
/* Because string byte lengths are in [0,DUK_INT_MAX] it's safe to
* subtract two string lengths without overflow.
*/
DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= DUK_INT_MAX);

h_target = duk_push_this_coercible_to_string(thr);
DUK_ASSERT(h_target != NULL);

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

magic = duk_get_current_magic(thr);

p_cmp_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h);
/* Careful to avoid pointer overflows in the matching logic. */

blen_target = DUK_HSTRING_GET_BYTELEN(h_target);
blen_search = DUK_HSTRING_GET_BYTELEN(h_search);

#if 0
/* If search string is longer than the target string, we can
* never match. Could check explicitly, but should be handled
* correctly below.
*/
if (blen_search > blen_target) {
goto finish;
}
#endif

off = 0;
if (duk_is_undefined(thr, 1)) {
if (magic) {
p_cmp_start = p_cmp_start + DUK_HSTRING_GET_BYTELEN(h) - blen_search;
off = (duk_int_t) blen_target - (duk_int_t) blen_search;
} else {
/* p_cmp_start already OK */
DUK_ASSERT(off == 0);
}
} else {
duk_int_t len;
duk_int_t pos;

DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= DUK_INT_MAX);
len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h);
len = (duk_int_t) DUK_HSTRING_GET_CHARLEN(h_target);
pos = duk_to_int_clamped(thr, 1, 0, len);
DUK_ASSERT(pos >= 0 && pos <= len);

off = (duk_int_t) duk_heap_strcache_offset_char2byte(thr, h_target, (duk_uint_fast32_t) pos);
if (magic) {
p_cmp_start -= blen_search; /* Conceptually subtracted last, but do already here. */
off -= (duk_int_t) blen_search;
}
DUK_ASSERT(pos >= 0 && pos <= len);

p_cmp_start += duk_heap_strcache_offset_char2byte(thr, h, (duk_uint_fast32_t) pos);
}
if (off < 0 || off > (duk_int_t) blen_target) {
goto finish;
}

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

result = 0;
if (p_cmp_start >= DUK_HSTRING_GET_DATA(h) &&
(duk_size_t) (p_cmp_start - (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h)) + blen_search <= DUK_HSTRING_GET_BYTELEN(h)) {
if (duk_memcmp((const void *) p_cmp_start,
(const void *) DUK_HSTRING_GET_DATA(h_search),
(size_t) blen_search) == 0) {
DUK_ASSERT(off >= 0);
DUK_ASSERT((duk_size_t) off <= blen_target);
blen_left = blen_target - (duk_size_t) off;
if (blen_left >= blen_search) {
const duk_uint8_t *p_cmp_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_target) + off;
const duk_uint8_t *p_search = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_search);
if (duk_memcmp_unsafe((const void *) p_cmp_start, (const void *) p_search, (size_t) blen_search) == 0) {
result = 1;
}
}

finish:
duk_push_boolean(thr, result);
return 1;
}
Expand Down
11 changes: 11 additions & 0 deletions tests/ecmascript/test-bug-string-endswith-memcmp-overflow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*===
done
===*/

try {
var v3 = "c".repeat(536870912);
var v4 = "base64".endsWith(v3);
} catch (e) {
print(e.stack || e);
}
print('done');

0 comments on commit aa81a68

Please sign in to comment.