Skip to content

Potential UAF using policydb_from_image/policydb_destroy #500

@hgarrereyn

Description

@hgarrereyn

Hi, there is a potential issue with policydb_from_image.

This bug was reproduced on dcdafad.

Description

The policydb_from_image function takes a new policydb_t argument to initialize. However in the case that the function fails (i.e. due to an invalid image), policydb_from_image invokes policydb_destroy directly, but does not zero out the policydb_t pointer.

This behavior is not documented, and users might expect that the caller is responsible for invoking policydb_destroy, which results in a use-after-free.

A simple solution here could be to zero-out the policydb_t argument when destroying (probably in policydb_destroy rather than conditionally in policydb_from_image). There is already a null pointer check in policydb_destroy so a second attempted destroy (from the caller) would become a nop.

POC

The following testcase demonstrates the bug:

testcase.cpp

#include <cstring>
extern "C" {
#include "/fuzz/install/include/sepol/handle.h"
#include "/fuzz/install/include/sepol/policydb/policydb.h"
}
int main(){
    sepol_handle_t *h = sepol_handle_create();
    if(!h) return 0;
    policydb_t p; if(policydb_init(&p)!=0) return 0;
    // Benign constant buffer; parse will fail but allocates internal tables
    unsigned char buf[136] = {0};
    const char magic[] = "SE Linux!"; // ASCII header, not expected magic
    memcpy(buf, magic, sizeof(magic)-1);
    (void)policydb_from_image(h, buf, sizeof(buf), &p); // returns error
    // Triggers heap-use-after-free in symtabs_destroy -> hashtab_map
    policydb_destroy(&p);
    return 0;
}

stdout


stderr

libsepol.policydb_read: policydb magic number 0x4c204553 does not match expected magic number 0xf97cff8c or 0xf97cff8d
libsepol.policydb_from_image: policy image is invalid
=================================================================
==1==ERROR: AddressSanitizer: heap-use-after-free on address 0x503000000048 at pc 0x55fa48e7822b bp 0x7fffcfd444b0 sp 0x7fffcfd444a8
READ of size 4 at 0x503000000048 thread T0
    #0 0x55fa48e7822a in hashtab_map (/fuzz/test+0x18c22a) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef)
    #1 0x55fa48e01e9d in symtabs_destroy (/fuzz/test+0x115e9d) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef)
    #2 0x55fa48e00f56 in policydb_destroy (/fuzz/test+0x114f56) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef)
    #3 0x55fa48dfe64a in main /fuzz/testcase.cpp:16:5
    #4 0x7f7bc966bd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #5 0x7f7bc966be3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #6 0x55fa48d23314 in _start (/fuzz/test+0x37314) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef)

0x503000000048 is located 8 bytes inside of 32-byte region [0x503000000040,0x503000000060)
freed by thread T0 here:
    #0 0x55fa48dbf416 in free (/fuzz/test+0xd3416) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef)
    #1 0x55fa48e01eb3 in symtabs_destroy (/fuzz/test+0x115eb3) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef)
    #2 0x55fa48dfe641 in main /fuzz/testcase.cpp:14:11
    #3 0x7f7bc966bd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

previously allocated by thread T0 here:
    #0 0x55fa48dbf6be in malloc (/fuzz/test+0xd36be) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef)
    #1 0x55fa48e774f8 in hashtab_create (/fuzz/test+0x18b4f8) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef)

SUMMARY: AddressSanitizer: heap-use-after-free (/fuzz/test+0x18c22a) (BuildId: 04031bba3eb95d32c3bedf5f81f619ef0457bbef) in hashtab_map
Shadow bytes around the buggy address:
  0x502ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x503000000000: fa fa 00 00 00 fa fa fa fd[fd]fd fd fa fa fd fd
  0x503000000080: fd fd fa fa fd fd fd fd fa fa fd fd fd fd fa fa
  0x503000000100: fd fd fd fd fa fa fd fd fd fd fa fa fd fd fd fd
  0x503000000180: fa fa fd fd fd fd fa fa fd fd fd fd fa fa fd fd
  0x503000000200: fd fd fa fa fd fd fd fd fa fa fd fd fd fd fa fa
  0x503000000280: fd fd fd fd fa fa fd fd fd fd fa fa fd fd fd fd
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
==1==ABORTING

Steps to Reproduce

The crash was triaged with the following Dockerfile:

Dockerfile

# Ubuntu 22.04 with some packages pre-installed
FROM hgarrereyn/stitch_repro_base@sha256:3ae94cdb7bf2660f4941dc523fe48cd2555049f6fb7d17577f5efd32a40fdd2c

RUN git clone https://github.com/SELinuxProject/selinux /fuzz/src && \
    cd /fuzz/src && \
    git checkout dcdafad74862d7aae8958a59d4022b519227df65 && \
    git submodule update --init --remote --recursive

ENV LD_LIBRARY_PATH=/fuzz/install/lib
ENV ASAN_OPTIONS=hard_rss_limit_mb=1024:detect_leaks=0

RUN echo '#!/bin/bash\nexec clang-17 -fsanitize=address -O0 "$@"' > /usr/local/bin/clang_wrapper && \
    chmod +x /usr/local/bin/clang_wrapper && \
    echo '#!/bin/bash\nexec clang++-17 -fsanitize=address -O0 "$@"' > /usr/local/bin/clang_wrapper++ && \
    chmod +x /usr/local/bin/clang_wrapper++

# Install minimal build deps (none needed beyond what's in base for static libsepol)
# Keep image small; avoid unnecessary packages. We disable CIL and shared libs to avoid flex/bison.

WORKDIR /fuzz/src

# Build and install only libsepol as a static library
ENV CC=clang_wrapper
ENV CXX=clang_wrapper++

RUN make -C libsepol clean || true \
 && make -C libsepol \
    DISABLE_SHARED=y \
    DISABLE_CIL=y \
    PREFIX=/fuzz/install \
    CC=${CC} \
    install

ENV PKG_CONFIG_PATH=/fuzz/install/lib/pkgconfig
ENV LD_LIBRARY_PATH=/fuzz/install/lib:$LD_LIBRARY_PATH

Build Command

clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lsepol && /fuzz/test

Reproduce

  1. Copy Dockerfile and testcase.cpp into a local folder.
  2. Build the repro image:
docker build . -t repro --platform=linux/amd64
  1. Compile and run the testcase in the image:
docker run \
    -it --rm \
    --platform linux/amd64 \
    --mount type=bind,source="$(pwd)/testcase.cpp",target=/fuzz/testcase.cpp \
    repro \
    bash -c "clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lsepol && /fuzz/test"


Additional Info

This testcase was discovered by STITCH, an autonomous fuzzing system. All reports are reviewed manually (by a human) before submission.

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