status-im / nim-faststreams

Nearly zero-overhead input/output streams for Nim
Apache License 2.0
125 stars 11 forks source link

avoid use-after-free in ARC and ORC #44

Closed tersec closed 1 year ago

tersec commented 1 year ago

This is easier to see with -d:useMalloc, but the ARC/ORC semantics are the same regardless, just typically a bit obscured by the pool memory allocations:

$ cat a.nim
import stew/ptrops

var x: ptr byte

block:
  var buffer: seq[byte] = @[100]
  buffer.setLen(1)
  x = addr buffer[0]

  echo buffer
  echo makeOpenArray(x, byte, 1)

echo makeOpenArray(x, byte, 1)
$ nim c -r --hints:off -d:useMalloc a
@[100]
[100]
[170]
$ nim c -r --hints:off -d:useMalloc a
@[100]
[100]
[214]
$ nim c -r --hints:off -d:useMalloc a
@[100]
[100]
[210]
$ nim c -r --hints:off -d:useMalloc a
@[100]
[100]
[187]
$ nim c -r --hints:off -d:useMalloc a
@[100]
[100]
[55]

One can also use ASAN in a repro of this (attached reduced repro from the `nim-faststreams mixed ASCII reading) to see this, a bit more in situ:

$ nim c -r --hints:off --debugger:native --mm:orc --passC:-fsanitize=address --passL:-fsanitize=address -d:useMalloc stew/ptrops.nim
...
==176194==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000178 at pc 0x55f047b0259f bp 0x7ffc228ce7e0 sp 0x7ffc228ce7d8
READ of size 1 at 0x602000000178 thread T0
    #0 0x55f047b0259e in str__ptrops_u1184 nim-stew/stew/ptrops.nim:310
    #1 0x55f047b03471 in NimMainModule nim-stew/stew/ptrops.nim:326
    #2 0x55f047b057ed in NimMainInner nim-stew/stew/ptrops.nim:214
    #3 0x55f047b05800 in NimMain nim-stew/stew/ptrops.nim:225
    #4 0x55f047b05822 in main nim-stew/stew/ptrops.nim:233
    #5 0x7fda44b67189 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #6 0x7fda44b67244 in __libc_start_main_impl ../csu/libc-start.c:381
    #7 0x55f047ae4360 in _start (nim-stew/stew/ptrops+0x9360) (BuildId: d5f5758f3c23f05d18da81aee9146f4eb6134290)

0x602000000178 is located 8 bytes inside of 10-byte region [0x602000000170,0x60200000017a)
freed by thread T0 here:
    #0 0x7fda44ed7298 in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x55f047ae9a42 in deallocImpl__system_u1784 Nim/lib/system/mm/malloc.nim:28

previously allocated by thread T0 here:
    #0 0x7fda44ed7fa7 in __interceptor_calloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:77
    #1 0x55f047ae99ee in alloc0Impl__system_u1782 Nim/lib/system/mm/malloc.nim:11

SUMMARY: AddressSanitizer: heap-use-after-free nim-stew/stew/ptrops.nim:310 in str__ptrops_u1184
Shadow bytes around the buggy address:
  0x601ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x601fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x601fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x602000000000: fa fa 00 03 fa fa fd fd fa fa fd fd fa fa fd fd
  0x602000000080: fa fa 00 02 fa fa fd fd fa fa fd fd fa fa 00 03
=>0x602000000100: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd[fd]
  0x602000000180: fa fa 00 03 fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x602000000380: 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
==176194==ABORTING
Error: execution of an external program failed: 'nim-stew/stew/ptrops'

The reason it was in a block to begin with was, as stated, to work around some template redefinition issue, but Nim 1.6 doesn't warn or error at all and later versions of Nim have options to specify that it's acceptable, courtesy of https://github.com/nim-lang/Nim/pull/20211.

ptrops.txt is slightly more faststream-realistic repro code showing the above UAF via ASAN.

tersec commented 1 year ago

macOS Nim 2.0/devel failure are separate, and at least it gets farther than in master:

[Suite] randomized tests
  [OK] randomized cursor test [memoryOutput(4064);writes=FixedSize,variance=0]
  [OK] randomized cursor test [memoryOutput(4064);writes=VarSize,variance=100]
  [OK] randomized cursor test [memoryOutput(10);writes=Mixed,variance=10]
  [OK] randomized file roundtrip
  [OK] ensureRunway
[SKIPPED] pipelines
Traceback (most recent call last)
/Users/runner/work/nim-faststreams/nim-faststreams/nim/lib/std/exitprocs.nim(49) callClosures
/Users/runner/.nimble/pkgs2/unittest2-0.0.7-b6d4a5cbe28b43c166d6442ba6804aafd4abe368/unittest2.nim(632) cleanupFormatters
/Users/runner/.nimble/pkgs2/unittest2-0.0.7-b6d4a5cbe28b43c166d6442ba6804aafd4abe368/unittest2.nim(311) testRunEnded
SIGSEGV: Illegal storage access. (Attempt to read from nil?)

and when running the new -d:useMalloc test, which tends to be stricter/hide fewer bugs.

etan-status commented 1 year ago

Thanks for catching this nasty one :)