Skip to content

global-oob-read in http_make_path via cupsCreateCredentials #125

@hgarrereyn

Description

@hgarrereyn

Hi, there is a potential bug in http_make_path reachable by invoking cupsCreateCredentials with root_name="".

This bug was reproduced on https://github.com/OpenPrinting/libcups.git/commit/881de116d71c695f125bf2d47acc3a988b9afaee.

Description

The failing code path is inside http_make_path (cups/tls.c) called from cupsCreateCredentials (tls-openssl.c). When assembling the filesystem path for the credential, the function computes a filename pointer and then unconditionally checks filename[-1] in the condition:
if (bufptr < bufend && filename[-1] != '.')

In cases where cupsCreateCredentials was invoked with a non-empty root_name, the filename pointer gets incremented before this. However, if it is "" then we end up dereferencing the byte before it.

I was not able to tell from the documentation whether it is legal to invoke cupsCreateCredentials in such a way. However, passing NULL is explicitly allowed and there's no mention that it cannot be the empty string.

POC

The following testcase demonstrates the bug:

testcase.cpp

#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cstring>
extern "C" {
#include "/fuzz/src/cups/cups.h"
}
int main(){
  const char *path = "/tmp";                 // writable directory
  bool ca_cert = false;
  cups_credpurpose_t purpose = (cups_credpurpose_t)0; // any value
  cups_credtype_t type = CUPS_CREDTYPE_RSA_2048_SHA256;
  cups_credusage_t usage = (cups_credusage_t)0;
  // Subject fields: only CN provided; others NULL
  const char *org = NULL, *orgu = NULL, *loc = NULL, *st = NULL, *c = NULL;
  const char *cn = "A";                       // minimal non-empty CN
  const char *email = NULL;
  size_t num_alt_names = 0; const char * const *alt_names = NULL;
  const char *root_name = "";                 // no root signer
  time_t expiration = 0;                       // any value
  (void)cupsCreateCredentials(path, ca_cert, purpose, type, usage,
                              org, orgu, loc, st, c,
                              cn, email, num_alt_names, alt_names,
                              root_name, expiration);
  return 0;
}

stdout

=================================================================
==1==ERROR: AddressSanitizer: global-buffer-overflow on address 0x560505fb8bdf at pc 0x560505f56cae bp 0x7ffdb6d38db0 sp 0x7ffdb6d38da8
READ of size 1 at 0x560505fb8bdf thread T0
    #0 0x560505f56cad in http_make_path /fuzz/src/cups/tls.c:717:26
    #1 0x560505f55d78 in cupsCreateCredentials /fuzz/src/cups/./tls-openssl.c:341:3
    #2 0x560505f552fa in main /fuzz/testcase.cpp:21:9
    #3 0x7fd55b159d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #4 0x7fd55b159e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #5 0x560505e7a0a4 in _start (/fuzz/test+0x640a4) (BuildId: 8cd322b62f88c85b5d1f21d4f8807283ce809055)

0x560505fb8bdf is located 1 bytes before global variable '.str.2' defined in '/fuzz/testcase.cpp:19' (0x560505fb8be0) of size 1
  '.str.2' is ascii string ''
0x560505fb8bdf is located 29 bytes after global variable '.str.1' defined in '/fuzz/testcase.cpp:16' (0x560505fb8bc0) of size 2
  '.str.1' is ascii string 'A'
SUMMARY: AddressSanitizer: global-buffer-overflow /fuzz/src/cups/tls.c:717:26 in http_make_path
Shadow bytes around the buggy address:
  0x560505fb8900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x560505fb8980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x560505fb8a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x560505fb8a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x560505fb8b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x560505fb8b80: 00 00 00 00 05 f9 f9 f9 02 f9 f9[f9]01 f9 f9 f9
  0x560505fb8c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x560505fb8c80: 03 f9 f9 f9 00 00 f9 f9 00 00 00 00 04 f9 f9 f9
  0x560505fb8d00: f9 f9 f9 f9 07 f9 f9 f9 04 f9 f9 f9 04 f9 f9 f9
  0x560505fb8d80: 03 f9 f9 f9 00 00 00 00 00 00 05 f9 f9 f9 f9 f9
  0x560505fb8e00: 00 00 00 03 f9 f9 f9 f9 00 00 00 04 f9 f9 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
==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/OpenPrinting/libcups.git /fuzz/src && \
    cd /fuzz/src && \
    git checkout 881de116d71c695f125bf2d47acc3a988b9afaee && \
    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 build dependencies
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
    pkg-config \
    libavahi-client-dev \
    libnss-mdns \
    libssl-dev \
    zlib1g-dev \
    libpng-dev \
    ca-certificates \
    make \
    && rm -rf /var/lib/apt/lists/*

# Provide a dummy pdfio pkg-config file so configure doesn't try to build the embedded submodule
# We won't build the tools that require PDFio, only the libcups library.
RUN mkdir -p /usr/local/lib/pkgconfig && \
    printf "prefix=/usr/local\nexec_prefix=\${prefix}\nlibdir=\${exec_prefix}/lib\nincludedir=\${prefix}/include\n\nName: pdfio\nDescription: Dummy PDFio for building libcups library only\nVersion: 1.6.0\nLibs: \nCflags: \n" > /usr/local/lib/pkgconfig/pdfio.pc

# Build and install libcups (static only) into /fuzz/install
WORKDIR /fuzz/src
ENV CC=clang_wrapper CXX=clang_wrapper++
RUN ./configure --prefix=/fuzz/install --disable-shared --enable-static --with-tls=openssl --disable-dbus
# Build only the library (skip tools that may need external PDFio)
RUN make -j"$(nproc)" DIRS=cups && make -j"$(nproc)" DIRS=cups install

Build Command

clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/src/cups -I/fuzz/install/include/cups -L/fuzz/install/lib -lcups3 -lavahi-client -lavahi-common -lssl -lcrypto -lz -lpthread -lm && /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="testcase.cpp",target=/fuzz/testcase.cpp \
    repro \
    bash -c "clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/src/cups -I/fuzz/install/include/cups -L/fuzz/install/lib -lcups3 -lavahi-client -lavahi-common -lssl -lcrypto -lz -lpthread -lm && /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

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions