Skip to content

encoder_base64_size: signed integer overflow #22320

Description

@xmoezzz

I did this

I reproduced this with a UBSan instrumented static build of the current curl master (87704c4) on macOS arm64.

1. Build a UBSan instrumented static libcurl

From the curl source directory:

cmake -S . -B build-ubsan-static \
  -DCMAKE_C_COMPILER=clang \
  -DCMAKE_BUILD_TYPE=Debug \
  -DBUILD_SHARED_LIBS=OFF \
  -DBUILD_STATIC_LIBS=ON \
  -DBUILD_CURL_EXE=OFF \
  -DBUILD_TESTING=OFF \
  -DBUILD_EXAMPLES=OFF \
  -DCMAKE_C_FLAGS="-O1 -g -fsanitize=undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer"

Build the static libcurl target:

cmake --build build-ubsan-static \
  --target libcurl_static \
  -j

The resulting static library was:

build-ubsan-static/lib/libcurl-d.a

2. Create the reproducer

I saved the following as mime_ubsan.c in the curl source directory:

#include <curl/curl.h>
#include <stdint.h>
#include <stdio.h>

static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg)
{
  (void)buffer;
  (void)size;
  (void)nitems;
  (void)arg;
  return 0;
}

int main(void)
{
  CURL *curl;
  curl_mime *mime;
  curl_mimepart *part;
  CURLcode result;

  curl_global_init(CURL_GLOBAL_DEFAULT);

  curl = curl_easy_init();
  mime = curl_mime_init(curl);
  part = curl_mime_addpart(mime);

  curl_mime_name(part, "data");
  curl_mime_data_cb(part, (curl_off_t)INT64_MAX,
                    read_cb, NULL, NULL, NULL);
  curl_mime_encoder(part, "base64");

  curl_easy_setopt(curl, CURLOPT_URL,
                   "http://127.0.0.1:18080/");
  curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);

  result = curl_easy_perform(curl);
  fprintf(stderr, "curl_easy_perform: %s\n",
          curl_easy_strerror(result));

  curl_mime_free(mime);
  curl_easy_cleanup(curl);
  curl_global_cleanup();

  return 0;
}

3. Link the reproducer against the local static libcurl

I placed the build directory first in PKG_CONFIG_PATH so that pkg-config used the libcurl.pc generated by this curl build:

export PKG_CONFIG_PATH="$PWD/build-ubsan-static:/opt/homebrew/lib/pkgconfig:/opt/homebrew/share/pkgconfig"

then:

bash <<'EOF'
set -e

clang \
  -O1 \
  -g \
  -fsanitize=undefined \
  -fno-sanitize-recover=undefined \
  -fno-omit-frame-pointer \
  -DCURL_STATICLIB \
  -I"$PWD/include" \
  "$PWD/mime_ubsan.c" \
  "$PWD/build-ubsan-static/lib/libcurl-d.a" \
  -L"$(brew --prefix libunistring)/lib" \
  $(pkg-config --static --libs libcurl | sed 's/-lcurl//g') \
  -o "$PWD/mime_ubsan"
EOF

4. Run the reproducer

I started a local HTTP server:

python3 -m http.server 18080

In another terminal, I ran:

UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \
  ./mime_ubsan

UBSan reported:

/omitted/curl-master/lib/mime.c:430:12:
runtime error: signed integer overflow:
4 * 3074457345618258603 cannot be represented in type
'curl_off_t' (aka 'long')

    #0 encoder_base64_size mime.c:430
    #1 mime_size mime.c:1597
    #2 mime_size mime.c:1592
    #3 Curl_creader_set_mime mime.c:2165
    #4 Curl_http http.c:3091
    #5 multi_runsingle multi.c:2813
    #6 multi_perform multi.c:2914
    #7 curl_easy_perform easy.c:820
    #8 main mime_ubsan.c:36

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior
lib/mime.c:430:12

The overflow occurs in:

size = 4 * (1 + ((size - 1) / 3));

The MIME size is calculated before the read callback is called, so the reproducer does not allocate or transfer INT64_MAX bytes.

I expected the following

The Base64 encoded size calculation should detect that the encoded size cannot be represented by curl_off_t and return an unknown size or an error without performing signed overflowing arithmetic.

curl/libcurl version

curl master (87704c4)

operating system

ProductName: macOS
ProductVersion: 26.5.2
BuildVersion: 25F84

Metadata

Metadata

Assignees

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