[BUG] heap-buffer-overflow in lutf8lib.c:220:14

144 views
Skip to first unread message

Kim Davies

unread,
Jul 12, 2025, 5:58:44 AMJul 12
to lua-l
heap-buffer-overflow in lutf8lib.c:220:14

### Environment:
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.1 LTS
Release:        24.04

### Compiler:
Ubuntu clang version 14.0.6
Target: x86_64-pc-linux-gnu
Thread model: posix

### Compiling:
```bash
CFLAGS="-fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -g" CXXFLAGS="-fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -g" make
```

### Version:
lua 5.5.0

### PoCfile:
Unzip poc.zip.

### Behavior:
```bash
./lua poc/lua_000.poc
```

Output:
```
=================================================================
==734932==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60300000209a at pc 0x56187eb97686 bp 0x7fffc3c23f50 sp 0x7fffc3c23f48
READ of size 1 at 0x60300000209a thread T0
    #0 0x56187eb97685 in byteoffset /home/exp/src/lua/lua/lutf8lib.c:220:14
    #1 0x56187ea07084 in precallC /home/exp/src/lua/lua/ldo.c:622:7
    #2 0x56187ea080a0 in luaD_precall /home/exp/src/lua/lua/ldo.c:691:7
    #3 0x56187eb16093 in luaV_execute /home/exp/src/lua/lua/lvm.c:1696:22
    #4 0x56187ea0a304 in ccall /home/exp/src/lua/lua/ldo.c:733:5
    #5 0x56187ea0a417 in luaD_callnoyield /home/exp/src/lua/lua/ldo.c:751:3
    #6 0x56187e9d9af4 in f_call /home/exp/src/lua/lua/lapi.c:1065:3
    #7 0x56187e9f9f3a in luaD_rawrunprotected /home/exp/src/lua/lua/ldo.c:158:3
    #8 0x56187ea0e554 in luaD_pcall /home/exp/src/lua/lua/ldo.c:1055:12
    #9 0x56187e9d8193 in lua_pcallk /home/exp/src/lua/lua/lapi.c:1091:14
    #10 0x56187e9b650e in docall /home/exp/src/lua/lua/lua.c:162:12
    #11 0x56187e9b620d in handle_script /home/exp/src/lua/lua/lua.c:266:14
    #12 0x56187e9b3820 in pmain /home/exp/src/lua/lua/lua.c:727:9
    #13 0x56187ea07084 in precallC /home/exp/src/lua/lua/ldo.c:622:7
    #14 0x56187ea080a0 in luaD_precall /home/exp/src/lua/lua/ldo.c:691:7
    #15 0x56187ea0a1cb in ccall /home/exp/src/lua/lua/ldo.c:731:13
    #16 0x56187ea0a417 in luaD_callnoyield /home/exp/src/lua/lua/ldo.c:751:3
    #17 0x56187e9d9af4 in f_call /home/exp/src/lua/lua/lapi.c:1065:3
    #18 0x56187e9f9f3a in luaD_rawrunprotected /home/exp/src/lua/lua/ldo.c:158:3
    #19 0x56187ea0e554 in luaD_pcall /home/exp/src/lua/lua/ldo.c:1055:12
    #20 0x56187e9d8193 in lua_pcallk /home/exp/src/lua/lua/lapi.c:1091:14
    #21 0x56187e9b3062 in main /home/exp/src/lua/lua/lua.c:755:12
    #22 0x7f551722a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #23 0x7f551722a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #24 0x56187e8f3594 in _start (/home/exp/bin/asan_ubsan/lua+0x13a594) (BuildId: b469b644ac3fe8d8c8ca3e4e4905b3e3863e1aed)

0x60300000209a is located 0 bytes to the right of 26-byte region [0x603000002080,0x60300000209a)
allocated by thread T0 here:
    #0 0x56187e9773ca in __interceptor_realloc (/home/exp/bin/asan_ubsan/lua+0x1be3ca) (BuildId: b469b644ac3fe8d8c8ca3e4e4905b3e3863e1aed)
    #1 0x56187eb31f75 in l_alloc /home/exp/src/lua/lua/lauxlib.c:1056:12
    #2 0x56187ea469e0 in luaM_malloc_ /home/exp/src/lua/lua/lmem.c:206:22
    #3 0x56187ea24035 in luaC_newobjdt /home/exp/src/lua/lua/lgc.c:299:13
    #4 0x56187ea245ca in luaC_newobj /home/exp/src/lua/lua/lgc.c:313:10
    #5 0x56187ea8b96a in createstrobj /home/exp/src/lua/lua/lstring.c:171:7
    #6 0x56187ea8d1fb in internshrstr /home/exp/src/lua/lua/lstring.c:235:8
    #7 0x56187ea8b14f in luaS_newlstr /home/exp/src/lua/lua/lstring.c:251:12
    #8 0x56187eb53d89 in luaX_newstring /home/exp/src/lua/lua/llex.c:157:24
    #9 0x56187eb63e96 in read_string /home/exp/src/lua/lua/llex.c:462:17
    #10 0x56187eb59bab in llex /home/exp/src/lua/lua/llex.c:541:9
    #11 0x56187eb55e40 in luaX_next /home/exp/src/lua/lua/llex.c:595:19
    #12 0x56187ea6bdf6 in funcargs /home/exp/src/lua/lua/lparser.c:1101:7
    #13 0x56187ea6520f in suffixedexp /home/exp/src/lua/lua/lparser.c:1201:9
    #14 0x56187ea60f4f in exprstat /home/exp/src/lua/lua/lparser.c:1901:3
    #15 0x56187ea5d05d in statement /home/exp/src/lua/lua/lparser.c:2029:7
    #16 0x56187ea5a03a in statlist /home/exp/src/lua/lua/lparser.c:860:5
    #17 0x56187ea56f3e in mainfunc /home/exp/src/lua/lua/lparser.c:2060:3
    #18 0x56187ea562fe in luaY_parser /home/exp/src/lua/lua/lparser.c:2084:3
    #19 0x56187ea109fa in f_parser /home/exp/src/lua/lua/ldo.c:1105:10
    #20 0x56187e9f9f3a in luaD_rawrunprotected /home/exp/src/lua/lua/ldo.c:158:3
    #21 0x56187ea0e554 in luaD_pcall /home/exp/src/lua/lua/ldo.c:1055:12
    #22 0x56187ea0f685 in luaD_protectedparser /home/exp/src/lua/lua/ldo.c:1122:12
    #23 0x56187e9d9c93 in lua_load /home/exp/src/lua/lua/lapi.c:1121:12
    #24 0x56187eb2f72a in luaL_loadfilex /home/exp/src/lua/lua/lauxlib.c:838:12
    #25 0x56187e9b61e3 in handle_script /home/exp/src/lua/lua/lua.c:263:12
    #26 0x56187e9b3820 in pmain /home/exp/src/lua/lua/lua.c:727:9
    #27 0x56187ea07084 in precallC /home/exp/src/lua/lua/ldo.c:622:7
    #28 0x56187ea080a0 in luaD_precall /home/exp/src/lua/lua/ldo.c:691:7
    #29 0x56187ea0a1cb in ccall /home/exp/src/lua/lua/ldo.c:731:13

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/exp/src/lua/lua/lutf8lib.c:220:14 in byteoffset
Shadow bytes around the buggy address:
  0x0c067fff83c0: fa fa fd fd fd fd fa fa 00 00 00 06 fa fa 00 00
  0x0c067fff83d0: 00 07 fa fa 00 00 00 07 fa fa 00 00 00 05 fa fa
  0x0c067fff83e0: 00 00 00 05 fa fa 00 00 00 05 fa fa 00 00 00 07
  0x0c067fff83f0: fa fa 00 00 00 06 fa fa 00 00 00 04 fa fa fd fd
  0x0c067fff8400: fd fd fa fa fd fd fd fa fa fa fd fd fd fa fa fa
=>0x0c067fff8410: 00 00 00[02]fa fa 00 00 00 02 fa fa fd fd fd fd
  0x0c067fff8420: fa fa 00 00 00 00 fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8430: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8440: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8450: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8460: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
==734932==ABORTING

```
poc.zip

mjmouse9999

unread,
Jul 14, 2025, 12:15:09 AMJul 14
to lua-l
On Saturday, July 12, 2025 at 7:58:44 PM UTC+10 Kim Davies wrote:
heap-buffer-overflow in lutf8lib.c:220:14
[...]
Unzip poc.zip.
[...]

The example (once unzipped and reformatted so that encoding doesn't break it) is:

    utf8.offset("\x9c", -1, q)

The code at the end of `byteoffset` in lutf8lib appears to have an overflow for skipping to final byte:

```
/* ... from line 215 of lutf8lib.c at git master (currently 848568790826b7e201f84682185b5b605c473016) */
/* this code was changed in 814213b65fa4ab2b1a7216d06f68a6f3df89efcd (utf8.offset returns also final position of character · lua/lua@814213b) */
  }
  lua_pushinteger(L, posi + 1);  /* initial position */
  if ((s[posi] & 0x80) != 0) {  /* multi-byte character? */
    do {
      posi++;
    } while (iscontp(s + posi + 1));  /* skip to final byte */ // <-- HERE
  }
  /* else one-byte character: final position is the initial one */
  lua_pushinteger(L, posi + 1);  /* 'posi' now is the final position */
  return 2;
```

For the single byte string, it appears to be a multibyte character, but the posi is incremented already, so the `iscontp` macro indexes `s[2]` which is past the null byte on the end.

Roberto Ierusalimschy

unread,
Jul 15, 2025, 1:04:40 PMJul 15
to lu...@googlegroups.com
> On Saturday, July 12, 2025 at 7:58:44 PM UTC+10 Kim Davies wrote:
>
> [...]
> The example (once unzipped and reformatted so that encoding doesn't break
> it) is:
>
> utf8.offset("\x9c", -1, q)
>
> The code at the end of `byteoffset` in lutf8lib appears to have an overflow
> for skipping to final byte:
>
> ```
> /* ... from line 215 of lutf8lib.c at git master
> (currently 848568790826b7e201f84682185b5b605c473016) */
> /* this code was changed in 814213b65fa4ab2b1a7216d06f68a6f3df89efcd (utf8.offset
> returns also final position of character · lua/lua@814213b
> <https://github.com/lua/lua/commit/814213b65fa4ab2b1a7216d06f68a6f3df89efcd>
> ) */
> }
> lua_pushinteger(L, posi + 1); /* initial position */
> if ((s[posi] & 0x80) != 0) { /* multi-byte character? */
> do {
> posi++;
> } while (iscontp(s + posi + 1)); /* skip to final byte */ // <-- HERE
> }
> /* else one-byte character: final position is the initial one */
> lua_pushinteger(L, posi + 1); /* 'posi' now is the final position */
> return 2;
> ```
>
> For the single byte string, it appears to be a multibyte character, but the
> posi is incremented already, so the `iscontp` macro indexes `s[2]` which is
> past the null byte on the end.

Many thanks for the detailed analysis (and to the OP too). That do-while
probably should be a 'while'.


-- Roberto
Reply all
Reply to author
Forward
0 new messages