Skip to content

Commit a2ff663

Browse files
authored
Test and fix for incompatible HEAP transforms in acorn-optimizer (#24309)
While working on #24295, I noticed I could accidentally prevent ASan and SAFE_HEAP from finding any `HEAPxx[n]` accesses from JS when `ALLOW_MEMORY_GROWTH` is enabled, and no tests would catch it, because no tests verify that acorn-optimizer passes which change `HEAPxx[n]` into something else can work in conjunction with each other. After adding the test, I found that an existing `SUPPORT_BIG_ENDIAN` feature was already broken in the same way, because it transforms `HEAPxx[n]` accesses into DataView methods, and since it was running before ASan and SAFE_HEAP, those latter transforms became no-ops. I fixed that in the same PR by moving the big-endian transform to the end. In the future we might want to consolidate those transforms in a more reliable way (e.g. only run a single code transform that does `HEAPxx[n]` -> `__loadHeap`/`__storeHeap` where those functions do all the work using `#if`s), but at least for now this PR fixes an immediate problem and catches potential future regressions.
1 parent fc311f3 commit a2ff663

11 files changed

+50
-30
lines changed

src/lib/libpthread.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,7 @@ var LibraryPThread = {
978978
979979
$establishStackSpace__internal: true,
980980
$establishStackSpace__deps: ['$stackRestore', 'emscripten_stack_set_limits'],
981-
$establishStackSpace: (pthread_ptr) => {
981+
$establishStackSpace: function (pthread_ptr) {
982982
var stackHigh = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack, '*') }}};
983983
var stackSize = {{{ makeGetValue('pthread_ptr', C_STRUCTS.pthread.stack_size, '*') }}};
984984
var stackLow = stackHigh - stackSize;

src/runtime_asan.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,23 @@
1212
// ASan instrumentation on them. However, until the wasm module is ready, we
1313
// must access things directly.
1414

15-
function _asan_js_load(arr, index) {
15+
function _asan_js_check_index(arr, index, asanFn) {
16+
#if EXIT_RUNTIME
17+
if (runtimeInitialized && !runtimeExited) {
18+
#else
1619
if (runtimeInitialized) {
20+
#endif
1721
const elemSize = arr.BYTES_PER_ELEMENT;
18-
___asan_loadN(index * elemSize, elemSize);
22+
asanFn(index * elemSize, elemSize);
1923
}
24+
}
25+
26+
function _asan_js_load(arr, index) {
27+
_asan_js_check_index(arr, index, ___asan_loadN);
2028
return arr[index];
2129
}
2230

2331
function _asan_js_store(arr, index, value) {
24-
if (runtimeInitialized) {
25-
const elemSize = arr.BYTES_PER_ELEMENT;
26-
___asan_storeN(index * elemSize, elemSize);
27-
}
32+
_asan_js_check_index(arr, index, ___asan_storeN);
2833
return arr[index] = value;
2934
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3785
1+
3786
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7816
1+
7827
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3985
1+
3989
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8247
1+
8258

test/test_core.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9084,14 +9084,6 @@ def test_asan_modularized_with_closure(self):
90849084
self.set_setting('INITIAL_MEMORY', '300mb')
90859085
self.do_run_in_out_file_test('hello_world.c')
90869086

9087-
@no_asan('SAFE_HEAP cannot be used with ASan')
9088-
@no_2gb('asan doesnt support GLOBAL_BASE')
9089-
@no_esm_integration('sanitizers do not support WASM_ESM_INTEGRATION')
9090-
def test_safe_heap_user_js(self):
9091-
self.set_setting('SAFE_HEAP')
9092-
self.do_runf('core/test_safe_heap_user_js.c',
9093-
expected_output=['Aborted(segmentation fault storing 1 bytes at address 0)'], assert_returncode=NON_ZERO)
9094-
90959087
def test_safe_stack(self):
90969088
self.set_setting('STACK_OVERFLOW_CHECK', 2)
90979089
self.set_setting('STACK_SIZE', 1024)

test/test_other.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12235,6 +12235,24 @@ def test_asan_strncpy(self):
1223512235
# https://github.com/emscripten-core/emscripten/issues/14618
1223612236
self.do_runf('other/test_asan_strncpy.c', emcc_args=['-fsanitize=address'])
1223712237

12238+
@parameterized({
12239+
'asan': ['AddressSanitizer: null-pointer-dereference', '-fsanitize=address'],
12240+
'safe_heap': ['Aborted(segmentation fault storing 1 bytes at address 0)', '-sSAFE_HEAP'],
12241+
})
12242+
@parameterized({
12243+
'': [],
12244+
'memgrowth': ['-pthread', '-sALLOW_MEMORY_GROWTH', '-Wno-pthreads-mem-growth'],
12245+
})
12246+
def test_null_deref_via_js(self, expected_output, *args):
12247+
# Multiple JS transforms look for pattern like `HEAPxx[...]` and transform it.
12248+
# This test ensures that one of the transforms doesn't produce a pattern that
12249+
# another pass can't find anymore, that is that features can work in conjunction.
12250+
self.do_runf(
12251+
'other/test_safe_heap_user_js.c',
12252+
emcc_args=args,
12253+
assert_returncode=NON_ZERO,
12254+
expected_output=[expected_output])
12255+
1223812256
@node_pthreads
1223912257
def test_proxy_to_pthread_stack(self):
1224012258
# Check that the proxied main gets run with STACK_SIZE setting and not

tools/acorn-optimizer.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1372,7 +1372,10 @@ function isHEAPAccess(node) {
13721372
function asanify(ast) {
13731373
recursiveWalk(ast, {
13741374
FunctionDeclaration(node, c) {
1375-
if (node.id.type === 'Identifier' && node.id.name.startsWith('_asan_js_')) {
1375+
if (
1376+
node.id.type === 'Identifier' &&
1377+
(node.id.name.startsWith('_asan_js_') || node.id.name === 'establishStackSpace')
1378+
) {
13761379
// do not recurse into this js impl function, which we use during
13771380
// startup before the wasm is ready
13781381
} else {

tools/link.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2322,31 +2322,33 @@ def phase_binaryen(target, options, wasm_target):
23222322
# after generating the wasm, do some final operations
23232323

23242324
if final_js:
2325-
if settings.SUPPORT_BIG_ENDIAN:
2326-
with ToolchainProfiler.profile_block('little_endian_heap'):
2327-
final_js = building.little_endian_heap(final_js)
2328-
23292325
# >=2GB heap support requires pointers in JS to be unsigned. rather than
23302326
# require all pointers to be unsigned by default, which increases code size
23312327
# a little, keep them signed, and just unsign them here if we need that.
23322328
if settings.CAN_ADDRESS_2GB:
23332329
with ToolchainProfiler.profile_block('use_unsigned_pointers_in_js'):
23342330
final_js = building.use_unsigned_pointers_in_js(final_js)
23352331

2332+
if settings.USE_ASAN:
2333+
final_js = building.instrument_js_for_asan(final_js)
2334+
2335+
if settings.SAFE_HEAP:
2336+
final_js = building.instrument_js_for_safe_heap(final_js)
2337+
23362338
# shared memory growth requires some additional JS fixups.
23372339
# note that we must do this after handling of unsigned pointers. unsigning
23382340
# adds some >>> 0 things, while growth will replace a HEAP8 with a call to
23392341
# a method to get the heap, and that call would not be recognized by the
2340-
# unsigning pass
2342+
# unsigning pass.
2343+
# we also must do this after the asan or safe_heap instrumentation, as they
2344+
# wouldn't be able to recognize patterns produced by the growth pass.
23412345
if settings.SHARED_MEMORY and settings.ALLOW_MEMORY_GROWTH:
23422346
with ToolchainProfiler.profile_block('apply_wasm_memory_growth'):
23432347
final_js = building.apply_wasm_memory_growth(final_js)
23442348

2345-
if settings.USE_ASAN:
2346-
final_js = building.instrument_js_for_asan(final_js)
2347-
2348-
if settings.SAFE_HEAP:
2349-
final_js = building.instrument_js_for_safe_heap(final_js)
2349+
if settings.SUPPORT_BIG_ENDIAN:
2350+
with ToolchainProfiler.profile_block('little_endian_heap'):
2351+
final_js = building.little_endian_heap(final_js)
23502352

23512353
if settings.OPT_LEVEL >= 2 and settings.DEBUG_LEVEL <= 2:
23522354
# minify the JS. Do not minify whitespace if Closure is used, so that

0 commit comments

Comments
 (0)