✓ProveIt
Examples / Report #2887487 Saved analysis
The false alarm

The report missed a size check.

Buffer Overflow Risk in Curl_inet_ntop and inet_ntop4

curl · 2024-12-0811.2s analysis · $0.014 in model calls

What the report claims

The report claims an unchecked copy can overflow a buffer.

Compare with curl's recorded outcome

Labeled AI slop

curl's maintainer lists this report as AI slop

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Code contradicts the claim

The real function checks the size and returns before the copy if the buffer is too small.

curl policy: API misuse
Why this finding?
  • The central claim (C2) that inet_ntop4 uses strcpy without ensuring the destination buffer is large enough is directly contradicted by the code at lib/inet_ntop.c:76, which checks 'if(len == 0 || len >= size)' before the strcpy at line 80.
  • The reporter's proof-of-concept code is not the actual curl implementation; it omits the critical bounds check that exists in the real code.
  • Both inet_ntop4 and inet_ntop6 perform runtime buffer size validation before copying to the destination buffer, making the claimed buffer overflow not reachable when the API is used as documented.
  • The project policy lists 'API misuse' as not a security issue: if an application passes a buffer smaller than documented requirements, that is misuse, not a curl vulnerability.
The key evidence

The guard before the copy

If the buffer is too small, the function returns. The copy comes after that check.

Inspect lib/inet_ntop.c:76 →
lib/inet_ntop.c@59fec5ac43adSource from the saved analysis
75 len = strlen(tmp);
76 if(len == 0 || len >= size) {
77 errno = ENOSPC;
78 return (NULL);
79 }
80 strcpy(dst, tmp);
81 return dst;
How this analysis ran 6 claims · 9 citations verified

Checked against curl master on 2024-12-08 (59fec5ac43ad). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 7 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
6 claims · 3332 tokens · $0.0003
3 · Evidence
4 code snippets
real curl source at 59fec5ac43ad
4 · Judgment
Nemotron-3-Ultra-550b-a55b
9271 tokens · 7.5s · $0.0137
5 · Receipts
9 of 9 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the report. The actual curl code in lib/inet_ntop.c includes a bounds check at line 76 (`if(len == 0 || len >= size)`) before the strcpy at line 80, which prevents the buffer overflow you describe. Your proof-of-concept omits this check. Both inet_ntop4 and inet_ntop6 validate the destination buffer size at runtime. When the API is used as documented (caller provides a buffer of at least 16 bytes for IPv4, 46 for IPv6), no overflow occurs. Per curl's policy, API misuse (passing undersized buffers) is not considered a security vulnerability in curl.

Inspect all claims and source code 6 claims · 4 code excerpts

Report claims

C1flaw supported

Curl_inet_ntop lacks buffer size validation before delegating to inet_ntop4 or inet_ntop6

Read the reasoning

Curl_inet_ntop does not perform buffer size validation before delegating; it passes the size parameter through to the helper functions which do validate.

C2flaw contradicted

inet_ntop4 uses strcpy without ensuring the destination buffer is large enough

Read the reasoning

The actual curl code at line 76 explicitly validates that the formatted string length is less than the destination buffer size before calling strcpy. The reporter's POC omits this check.

C3flaw supported

Minimum buffer size of 16 bytes is required for IPv4 addresses like "255.255.255.255\0"

Read the reasoning

16 bytes is correct for the maximum IPv4 string representation including null terminator.

C4flaw supported

Minimum buffer size of 46 bytes is required for IPv6 addresses like "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\0"

Read the reasoning

46 bytes is the correct minimum for full IPv6 address representation.

C5flaw supported

Assertions (DEBUGASSERT) in inet_ntop4 are disabled in production builds, removing safety checks

Read the reasoning

DEBUGASSERT is indeed disabled in production builds, but a runtime check at line 76 remains active.

C6flaw contradicted

snprintf is used to format IPv4 addresses but does not guarantee safety if the buffer is too small

Read the reasoning

msnprintf writes to a sufficiently large temporary buffer; the destination buffer size is validated at line 76 before any copy occurs.

Read the original report (excerpt)

Full report on HackerOne ↗

*Curl is a software that I love and is an important tool for the world. * *If my report doesn't align, I apologize for that.* The `Curl_inet_ntop` function is designed to convert IP addresses from binary format to human-readable string format, supporting both IPv4 and IPv6. It internally delegates to `inet_ntop4` for IPv4 addresses and `inet_ntop6` for IPv6 addresses. However, insufficient validation of buffer size (`buf`) in these functions exposes the implementation to **buffer overflow risks**, which can lead to undefined behavior, application crashes, or security vulnerabilities. This report analyzes vulnerabilities in both `Curl_inet_ntop` and `inet_ntop4`, demonstrates proof-of-concept (POC) exploits, and proposes mitigation strategies. ## **Vulnerability Analysis** ### **Root Cause** The vulnerabilities stem from: 1. **`Curl_inet_ntop`:** Lack of buffer size validation before delegating to `inet_ntop4` or `inet_ntop6`. 2. **`inet_ntop4`:** Direct use of `strcpy` without ensuring that the destination buffer (`dst`) is large enough. ### **Key Points of Failure** 1. **Buffer Size Mismatch:** - For IPv4, a minimum of 16 bytes is required for `"255.255.255.255\0"`. - For IPv6, a minimum of 46 bytes is required for `"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff\0"`. - Both `Curl_inet_ntop` and `inet_ntop4` assume that the caller provides a sufficiently large buffer without explicit validation. 2. **Unsafe String Operations in `inet_ntop4`:** - `inet_ntop4` uses `strcpy(dst, tmp)` to copy the temporary buffer `tmp` into `dst`, which can overflow if `dst` is too small. 3. **Production Vulnerabilities:** - Assertions (`DEBUGASSERT`) in `inet_ntop4` are disabled in production builds, removing critical safety checks. ## **Proof-of-Concept (POC)** ### **Test for `inet_ntop4`** #### **Vulnerable Code** ```c #include <stdio.h> #include <string.h> #include <errno.h> static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) { char tmp[sizeof("255.255.255.255")]; snprintf(tmp, sizeof(tmp), "%d.%d.%d.%d", src[0], src[1], src[2], src[3]); if (strlen(tmp) >= size) { errno = ENOSPC; return NULL; } strcpy(dst, tmp); // Vulnerable to overflow return dst; } int main() { unsigned char ipv4[4] = {192, 168, 0, 1}; char small_buffer[10]; // Intentionally too small // Test w …

Source code

lib/inet_ntop.clines 61–82 · definition of inet_ntop4
61static char *inet_ntop4(const unsigned char *src, char *dst, size_t size)
62{
63 char tmp[sizeof("255.255.255.255")];
C3: tmp buffer sized as sizeof("255.255.255.255") = 16 bytes including null terminator
64 size_t len;
65
66 DEBUGASSERT(size >= 16);
C3: DEBUGASSERT(size >= 16) documents the minimum
C5: DEBUGASSERT(size >= 16) in inet_ntop4
67
68 tmp[0] = '\0';
69 (void)msnprintf(tmp, sizeof(tmp), "%d.%d.%d.%d",
C6: msnprintf writes to tmp buffer sized for maximum IPv4 string (16 bytes), not directly to destination
70 ((int)((unsigned char)src[0])) & 0xff,
71 ((int)((unsigned char)src[1])) & 0xff,
72 ((int)((unsigned char)src[2])) & 0xff,
73 ((int)((unsigned char)src[3])) & 0xff);
74
75 len = strlen(tmp);
76 if(len == 0 || len >= size) {
C2: inet_ntop4 checks 'if(len == 0 || len >= size)' before strcpy at line 80, ensuring destination buffer is large enough
C6: Runtime check 'len >= size' validates destination buffer before copy
77 errno = ENOSPC;
78 return (NULL);
79 }
80 strcpy(dst, tmp);
81 return dst;
82}
lib/inet_ntop.clines 192–203 · definition of Curl_inet_ntop
192char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size)
C1: Curl_inet_ntop at line 192-202 delegates directly to inet_ntop4/inet_ntop6 without its own size validation
193{
194 switch(af) {
195 case AF_INET:
196 return inet_ntop4((const unsigned char *)src, buf, size);
197 case AF_INET6:
198 return inet_ntop6((const unsigned char *)src, buf, size);
199 default:
200 errno = EAFNOSUPPORT;
201 return NULL;
202 }
203}
lib/curl_setup_once.hlines 294–294 · definition of DEBUGASSERT
294#define DEBUGASSERT(x) assert(x)
C5: DEBUGASSERT defined as assert(x), which is disabled when NDEBUG is defined (production builds)
lib/inet_ntop.clines 87–179 · definition of inet_ntop6
87static char *inet_ntop6(const unsigned char *src, char *dst, size_t size)
88{
89 /*
90 * Note that int32_t and int16_t need only be "at least" large enough
91 * to contain a value of the specified size. On some systems, like
92 * Crays, there is no such thing as an integer variable with 16 bits.
93 * Keep this in mind if you think this function should have been coded
94 * to use pointer overlays. All the world's not a VAX.
95 */
96 char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
C4: tmp buffer in inet_ntop6 sized for maximum IPv6 representation including embedded IPv4
97 char *tp;
98 struct {
99 int base;
100 int len;
101 } best, cur;
102 unsigned int words[IN6ADDRSZ / INT16SZ];
103 int i;
104
105 /* Preprocess:
106 * Copy the input (bytewise) array into a wordwise array.
107 * Find the longest run of 0x00's in src[] for :: shorthanding.
108 */
109 memset(words, '\0', sizeof(words));
110 for(i = 0; i < IN6ADDRSZ; i++)
111 words[i/2] |= ((unsigned int)src[i] << ((1 - (i % 2)) << 3));
112
113 best.base = -1;
114 cur.base = -1;
115 best.len = 0;
116 cur.len = 0;
117
118 for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
119 if(words[i] == 0) {
120 if(cur.base == -1) {
121 cur.base = i; cur.len = 1;
122 }
123 else
124 cur.len++;
125 }
126 else if(cur.base != -1) {
127 if(best.base == -1 || cur.len > best.len)
128 best = cur;
129 cur.base = -1;
130 }
131 }
132 if((cur.base != -1) && (best.base == -1 || cur.len > best.len))
133 best = cur;
134 if(best.base != -1 && best.len < 2)
135 best.base = -1;
136 /* Format the result. */
137 tp = tmp;
138 for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
139 /* Are we inside the best run of 0x00's? */
140 if(best.base != -1 && i >= best.base && i < (best.base + best.len)) {
141 if(i == best.base)
142 *tp++ = ':';
143 continue;
144 }
145
146 /* Are we following an initial run of 0x00s or any real hex?
147 */
148 if(i)
149 *tp++ = ':';
150
151 /* Is this address an encapsulated IPv4?
152 */
153 if(i == 6 && best.base == 0 &&
154 (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) {
155 if(!inet_ntop4(src + 12, tp, sizeof(tmp) - (tp - tmp))) {
156 errno = ENOSPC;
157 return (NULL);
158 }
159 tp += strlen(tp);
160 break;
161 }
162 tp += msnprintf(tp, 5, "%x", words[i]);
163 }
164
165 /* Was it a trailing run of 0x00's?
166 */
167 if(best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ))
168 *tp++ = ':';
169 *tp++ = '\0';
170
171 /* Check for overflow, copy, and we are done.
172 */
173 if((size_t)(tp - tmp) > size) {
174 errno = ENOSPC;
175 return (NULL);
176 }
177 strcpy(dst, tmp);
178 return dst;
179}