Skip to content

[Bug] Global-buffer-overflow in emitOp accessing stackEffects array #1219

@oneafter

Description

@oneafter

Description

We discovered a Global-buffer-overflow vulnerability in the Wren compiler. The crash occurs in the emitOp function when it attempts to access the global stackEffects array using an invalid or out-of-bounds opcode index.

The ASAN report indicates a READ violation of size 4, occurring exactly 4 bytes after the end of the global stackEffects array.

Environment

  • OS: Linux x86_64
  • Complier: Clang
  • Build Configuration: Release mode with ASan enabled.

Vulnerability Details

  • Target: Wren (wren-lang)
  • Vulnerability Type: CWE-125: Out-of-bounds Read
  • Function: emitOp
  • Location: src/vm/wren_compiler.c:1329
  • Global Variable: stackEffects (defined at src/vm/wren_compiler.c:414)
  • Root Cause Analysis: The function emitOp likely uses the passed instruction (opcode) as an index to lookup stack adjustments:
// Example code in emitOp
int effect = stackEffects[instruction];

The ASAN report shows the read happens at 0x55ab9c6f8558, which is 4 bytes after the 308-byte stackEffects array. This implies that the compiler attempted to emit an instruction with an ID corresponding to MAX_OPCODES (or one slot past the valid range), causing the array index out-of-bounds read.

The call stack points to subscript -> callSignature -> emitShortArg -> emitOp. This suggests the issue arises when compiling a subscript/method call that requires a 16-bit argument (Extended Opcode), but the opcode calculation is incorrect.

Reproduce

  1. Build wren and harness with Release optimization and ASAN enabled.
harness.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wren.h"

void writeFn(WrenVM* vm, const char* text) {
   
}

void errorFn(WrenVM* vm, WrenErrorType type, const char* module, int line, const char* message) {
   
}

int main(int argc, char** argv) {
    if (argc < 2) return 1;
    
    FILE* f = fopen(argv[1], "rb");
    if (!f) return 1;
    
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    
    char* buffer = (char*)malloc(length + 1);
    if (!buffer) {
        fclose(f);
        return 1;
    }
    
    if (fread(buffer, 1, length, f) != (size_t)length) {
        free(buffer);
        fclose(f);
        return 1;
    }
    buffer[length] = '\0';
    fclose(f);

    WrenConfiguration config;
    wrenInitConfiguration(&config);
    config.writeFn = writeFn;
    config.errorFn = errorFn;

    WrenVM* vm = wrenNewVM(&config);

    WrenInterpretResult result = wrenInterpret(vm, "main", buffer);

    wrenFreeVM(vm);
    free(buffer);

    return 0;
}
  1. Run with the crashing file:
./bin/harness repro
ASAN report
==8453==ERROR: AddressSanitizer: global-buffer-overflow on address 0x55a049c3f558 at pc 0x55a049bd9ca7 bp 0x7ffdd6dd1190 sp 0x7ffdd6dd1188
READ of size 4 at 0x55a049c3f558 thread T0
    #0 0x55a049bd9ca6 in emitOp /src/wren/projects/make/../../src/vm/wren_compiler.c:1329:25
    #1 0x55a049bd9ca6 in emitShortArg /src/wren/projects/make/../../src/vm/wren_compiler.c:1355:3
    #2 0x55a049bc554b in callSignature /src/wren/projects/make/../../src/vm/wren_compiler.c:1984:3
    #3 0x55a049bc554b in subscript /src/wren/projects/make/../../src/vm/wren_compiler.c:2532:3
    #4 0x55a049be16ba in parsePrecedence /src/wren/projects/make/../../src/vm/wren_compiler.c:2855:5
    #5 0x55a049be16ba in expression /src/wren/projects/make/../../src/vm/wren_compiler.c:2863:3
    #6 0x55a049be16ba in bareName /src/wren/projects/make/../../src/vm/wren_compiler.c:2313:5
    #7 0x55a049bd536c in parsePrecedence /src/wren/projects/make/../../src/vm/wren_compiler.c:2849:3
    #8 0x55a049bd536c in expression /src/wren/projects/make/../../src/vm/wren_compiler.c:2863:3
    #9 0x55a049beeaa3 in statement /src/wren/projects/make/../../src/vm/wren_compiler.c:3266:5
    #10 0x55a049bd5d7b in definition /src/wren/projects/make/../../src/vm/wren_compiler.c:3764:5
    #11 0x55a049bcf03d in wrenCompile /src/wren/projects/make/../../src/vm/wren_compiler.c:3815:7
    #12 0x55a049bbbaa3 in compileInModule /src/wren/projects/make/../../src/vm/wren_vm.c:484:15
    #13 0x55a049bbaf26 in wrenCompileSource /src/wren/projects/make/../../src/vm/wren_vm.c:1538:25
    #14 0x55a049bbaf26 in wrenInterpret /src/wren/projects/make/../../src/vm/wren_vm.c:1517:25
    #15 0x55a049bafca5 in main /src/wren/fuzz_wren.c:51:34
    #16 0x7efc834031c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
    #17 0x7efc8340328a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
    #18 0x55a049ace5c4 in _start (/src/wren/bin/fuzz_wren+0x365c4) (BuildId: 5d78be029a4b6a34067ee0d0f65b83b8780504cc)

0x55a049c3f558 is located 4 bytes after global variable 'stackEffects' defined in '/src/wren/projects/make/../../src/vm/wren_compiler.c:414' (0x55a049c3f420) of size 308
SUMMARY: AddressSanitizer: global-buffer-overflow /src/wren/projects/make/../../src/vm/wren_compiler.c:1329:25 in emitOp
Shadow bytes around the buggy address:
  0x55a049c3f280: f9 f9 f9 f9 00 03 f9 f9 00 00 01 f9 f9 f9 f9 f9
  0x55a049c3f300: 00 00 07 f9 f9 f9 f9 f9 00 00 00 07 f9 f9 f9 f9
  0x55a049c3f380: 00 00 00 02 f9 f9 f9 f9 00 00 00 00 02 f9 f9 f9
  0x55a049c3f400: f9 f9 f9 f9 00 00 00 00 00 00 00 00 00 00 00 00
  0x55a049c3f480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x55a049c3f500: 00 00 00 00 00 00 00 00 00 00 04[f9]f9 f9 f9 f9
  0x55a049c3f580: f9 f9 f9 f9 00 00 00 00 07 f9 f9 f9 02 f9 f9 f9
  0x55a049c3f600: 06 f9 f9 f9 06 f9 f9 f9 06 f9 f9 f9 00 01 f9 f9
  0x55a049c3f680: 05 f9 f9 f9 06 f9 f9 f9 05 f9 f9 f9 00 02 f9 f9
  0x55a049c3f700: 00 03 f9 f9 00 00 01 f9 f9 f9 f9 f9 00 02 f9 f9
  0x55a049c3f780: 05 f9 f9 f9 06 f9 f9 f9 07 f9 f9 f9 00 01 f9 f9
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==8453==ABORTING

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions