Heap Overflow in cURL AmigaOS Socket Implementation
What the report claims
Heap overflow in Curl_ipv4_resolve_r in AmigaOS socket backend
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
The code does what the report says, but curl's own policy may exclude it. A maintainer decides.
curl policy: API misuseSee the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- The curl code correctly allocates a buffer and passes its size to gethostbyname_r; any buffer overflow would be in the AmigaOS system library, not in curl's implementation.
- The report's alignment/padding argument is speculative without the gethostbyname_r specification or implementation.
- Issue C (resource leak) is contradicted by the code: curlx_free(buf) is called on all paths after allocation.
- The project policy lists 'API misuse' as not a security issue: curl uses the system API as documented (passing buffer and size).
- No evidence in the provided code that 9000 bytes is insufficient for legitimate DNS responses on AmigaOS.
What still needs checking
- The AmigaOS bsdsocket.library gethostbyname_r implementation or documentation showing it ignores the buffer size parameter.
- Evidence that legitimate DNS responses on AmigaOS exceed 9000 bytes in the hostent structure.
How this analysis ran 5 claims · 7 citations verified
Evidence check: the behavior is real; the project's policy section 'API misuse' may exclude it
Checked against curl master on 2025-12-16 (7b2783496ce7). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Inspect the withdrawn draft reply
This draft belongs to the withdrawn judgment. It may contain unsupported claims.
Thank you for the detailed report. The curl code in lib/amigaos.c correctly passes the buffer size (CURL_HOSTENT_SIZE - sizeof(struct hostent)) to gethostbyname_r, so any overflow would be in the AmigaOS system library, not in curl. The alignment argument assumes internal padding in gethostbyname_r without its specification. Issue C is contradicted: curlx_free(buf) runs on all paths. To proceed, we would need evidence that gethostbyname_r ignores the buffer size limit or that 9000 bytes is too small for valid AmigaOS DNS responses. Could you provide the bsdsocket.library documentation or a test showing the overflow with a legitimate (non-malicious) response?
Inspect all claims and source code 5 claims · 7 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
Fixed buffer size of 8192 bytes allocated for gethostbyname_r response without considering actual DNS response size
Read the reasoning
The curl code allocates a 9000-byte buffer and correctly passes the data buffer size to gethostbyname_r. Whether this causes an overflow depends on the behavior of gethostbyname_r (an AmigaOS bsdsocket.library function), which is not provided in the curl source. The claim assumes gethostbyname_r ignores the buffer size limit, but no evidence of this is in the provided code.
Function Curl_ipv4_resolve_r in lib/amigaos.c
Read the reasoning
The function Curl_ipv4_resolve_r exists in lib/amigaos.c at the stated location.
Providing specially crafted hostnames or DNS responses that exceed the fixed buffer size
Read the reasoning
The trigger depends on gethostbyname_r not respecting the buffer size limit. Without the system library implementation, this cannot be verified from curl code alone.
Remote code execution via heap metadata corruption
Read the reasoning
Remote code execution via heap corruption requires a demonstrated overflow in curl's code. The provided curl code shows correct buffer size passing; any overflow would be in the external gethostbyname_r implementation, not shown.
Use long hostnames (4000+ characters) or multiple DNS records to overflow the buffer
Read the reasoning
The reproduction references MAX_HOSTNAME_LEN which does not exist in the current code (the report's patch proposes adding it). The current code has no hostname length validation, but the trigger still depends on the unproven gethostbyname_r behavior.
Deterministic checks that failed
- symbol _Static_assert() does not exist in curl at 7b2783496ce7 or in any release since 7.50
- symbol Curl_getaddrinfo exists in other curl releases but not at 7b2783496ce7
- symbol PPC exists in other curl releases but not at 7b2783496ce7
- quote only 0/2 quoted lines appear anywhere in curl at 7b2783496ce7
- quote 1 of 3 quoted lines appear nowhere in curl, e.g. 'if(data_size < MIN_SAFE_DNS_BUFFER) {'
- quote 1 of 2 quoted lines appear nowhere in curl, e.g. 'return Curl_getaddrinfo(hostname, port);'
- quote only 0/12 quoted lines appear anywhere in curl at 7b2783496ce7
Read the original report (excerpt)
Source code
| 45 | #define CURL_HOSTENT_SIZE 9000 |
| C1: CURL_HOSTENT_SIZE defined as 9000 |
| 123 | struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port) |
| C2: Function Curl_ipv4_resolve_r defined at line 123 | |
| C5: No MAX_HOSTNAME_LEN constant exists in the function | |
| 124 | { |
| 125 | struct Curl_addrinfo *ai = NULL; |
| 126 | struct hostent *h; |
| 127 | struct SocketIFace *ISocket = __CurlISocket; |
| 128 | |
| 129 | if(SocketFeatures & HAVE_BSDSOCKET_GETHOSTBYNAME_R) { |
| 130 | LONG h_errnop = 0; |
| 131 | struct hostent *buf; |
| 132 | |
| 133 | buf = curlx_calloc(1, CURL_HOSTENT_SIZE); |
| C1: Buffer allocated with curlx_calloc(1, CURL_HOSTENT_SIZE) | |
| C4: Heap allocation site | |
| 134 | if(buf) { |
| 135 | h = gethostbyname_r((STRPTR)hostname, buf, |
| C1: gethostbyname_r called with data buffer size CURL_HOSTENT_SIZE - sizeof(struct hostent) | |
| C3: hostname passed to gethostbyname_r | |
| 136 | (char *)buf + sizeof(struct hostent), |
| 137 | CURL_HOSTENT_SIZE - sizeof(struct hostent), |
| 138 | &h_errnop); |
| 139 | if(h) { |
| 140 | ai = Curl_he2ai(h, port); |
| 141 | } |
| 142 | curlx_free(buf); |
| 143 | } |
| 144 | } |
| 145 | else { |
| 146 | #ifdef CURLRES_THREADED |
| 147 | /* gethostbyname() is not thread safe, so we need to reopen bsdsocket |
| 148 | * on the thread's context |
| 149 | */ |
| 150 | struct Library *base = OpenLibrary("bsdsocket.library", 4); |
| 151 | if(base) { |
| 152 | ISocket = (struct SocketIFace *)GetInterface(base, "main", 1, NULL); |
| 153 | if(ISocket) { |
| 154 | h = gethostbyname((STRPTR)hostname); |
| 155 | if(h) { |
| 156 | ai = Curl_he2ai(h, port); |
| 157 | } |
| 158 | DropInterface((struct Interface *)ISocket); |
| 159 | } |
| 160 | CloseLibrary(base); |
| 161 | } |
| 162 | #else |
| 163 | /* not using threaded resolver - safe to use this as-is */ |
| 164 | h = gethostbyname(hostname); |
| 165 | if(h) { |
| 166 | ai = Curl_he2ai(h, port); |
| 167 | } |
| 168 | #endif |
| 169 | } |
| 170 | |
| 171 | return ai; |
| 172 | } |
Show 51 lines (not cited by the judge)
| 127 | struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, |
| 128 | int port) |
| 129 | { |
| 130 | struct Curl_addrinfo *ai = NULL; |
| 131 | struct hostent *h; |
| 132 | struct SocketIFace *ISocket = __CurlISocket; |
| 133 | |
| 134 | if(SocketFeatures & HAVE_BSDSOCKET_GETHOSTBYNAME_R) { |
| 135 | LONG h_errnop = 0; |
| 136 | struct hostent *buf; |
| 137 | |
| 138 | buf = calloc(1, CURL_HOSTENT_SIZE); |
| 139 | if(buf) { |
| 140 | h = gethostbyname_r((STRPTR)hostname, buf, |
| 141 | (char *)buf + sizeof(struct hostent), |
| 142 | CURL_HOSTENT_SIZE - sizeof(struct hostent), |
| 143 | &h_errnop); |
| 144 | if(h) { |
| 145 | ai = Curl_he2ai(h, port); |
| 146 | } |
| 147 | free(buf); |
| 148 | } |
| 149 | } |
| 150 | else { |
| 151 | #ifdef CURLRES_THREADED |
| 152 | /* gethostbyname() is not thread safe, so we need to reopen bsdsocket |
| 153 | * on the thread's context |
| 154 | */ |
| 155 | struct Library *base = OpenLibrary("bsdsocket.library", 4); |
| 156 | if(base) { |
| 157 | ISocket = (struct SocketIFace *)GetInterface(base, "main", 1, NULL); |
| 158 | if(ISocket) { |
| 159 | h = gethostbyname((STRPTR)hostname); |
| 160 | if(h) { |
| 161 | ai = Curl_he2ai(h, port); |
| 162 | } |
| 163 | DropInterface((struct Interface *)ISocket); |
| 164 | } |
| 165 | CloseLibrary(base); |
| 166 | } |
| 167 | #else |
| 168 | /* not using threaded resolver - safe to use this as-is */ |
| 169 | h = gethostbyname(hostname); |
| 170 | if(h) { |
| 171 | ai = Curl_he2ai(h, port); |
| 172 | } |
| 173 | #endif |
| 174 | } |
| 175 | |
| 176 | return ai; |
| 177 | } |
Show 89 lines (not cited by the judge)
| 246 | struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port) |
| 247 | { |
| 248 | struct Curl_addrinfo *ai; |
| 249 | struct Curl_addrinfo *prevai = NULL; |
| 250 | struct Curl_addrinfo *firstai = NULL; |
| 251 | struct sockaddr_in *addr; |
| 252 | #ifdef USE_IPV6 |
| 253 | struct sockaddr_in6 *addr6; |
| 254 | #endif |
| 255 | CURLcode result = CURLE_OK; |
| 256 | int i; |
| 257 | char *curr; |
| 258 | |
| 259 | if(!he) |
| 260 | /* no input == no output! */ |
| 261 | return NULL; |
| 262 | |
| 263 | DEBUGASSERT((he->h_name != NULL) && (he->h_addr_list != NULL)); |
| 264 | |
| 265 | for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { |
| 266 | size_t ss_size; |
| 267 | size_t namelen = strlen(he->h_name) + 1; /* include null-terminator */ |
| 268 | #ifdef USE_IPV6 |
| 269 | if(he->h_addrtype == AF_INET6) |
| 270 | ss_size = sizeof(struct sockaddr_in6); |
| 271 | else |
| 272 | #endif |
| 273 | ss_size = sizeof(struct sockaddr_in); |
| 274 | |
| 275 | /* allocate memory to hold the struct, the address and the name */ |
| 276 | ai = curlx_calloc(1, sizeof(struct Curl_addrinfo) + ss_size + namelen); |
| 277 | if(!ai) { |
| 278 | result = CURLE_OUT_OF_MEMORY; |
| 279 | break; |
| 280 | } |
| 281 | /* put the address after the struct */ |
| 282 | ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo)); |
| 283 | /* then put the name after the address */ |
| 284 | ai->ai_canonname = (char *)ai->ai_addr + ss_size; |
| 285 | memcpy(ai->ai_canonname, he->h_name, namelen); |
| 286 | |
| 287 | if(!firstai) |
| 288 | /* store the pointer we want to return from this function */ |
| 289 | firstai = ai; |
| 290 | |
| 291 | if(prevai) |
| 292 | /* make the previous entry point to this */ |
| 293 | prevai->ai_next = ai; |
| 294 | |
| 295 | ai->ai_family = he->h_addrtype; |
| 296 | |
| 297 | /* we return all names as STREAM, so when using this address for TFTP |
| 298 | the type must be ignored and conn->socktype be used instead! */ |
| 299 | ai->ai_socktype = SOCK_STREAM; |
| 300 | |
| 301 | ai->ai_addrlen = (curl_socklen_t)ss_size; |
| 302 | |
| 303 | /* leave the rest of the struct filled with zero */ |
| 304 | |
| 305 | switch(ai->ai_family) { |
| 306 | case AF_INET: |
| 307 | addr = (void *)ai->ai_addr; /* storage area for this info */ |
| 308 | |
| 309 | memcpy(&addr->sin_addr, curr, sizeof(struct in_addr)); |
| 310 | addr->sin_family = (CURL_SA_FAMILY_T)(he->h_addrtype); |
| 311 | addr->sin_port = htons((unsigned short)port); |
| 312 | break; |
| 313 | |
| 314 | #ifdef USE_IPV6 |
| 315 | case AF_INET6: |
| 316 | addr6 = (void *)ai->ai_addr; /* storage area for this info */ |
| 317 | |
| 318 | memcpy(&addr6->sin6_addr, curr, sizeof(struct in6_addr)); |
| 319 | addr6->sin6_family = (CURL_SA_FAMILY_T)(he->h_addrtype); |
| 320 | addr6->sin6_port = htons((unsigned short)port); |
| 321 | break; |
| 322 | #endif |
| 323 | } |
| 324 | |
| 325 | prevai = ai; |
| 326 | } |
| 327 | |
| 328 | if(result) { |
| 329 | Curl_freeaddrinfo(firstai); |
| 330 | firstai = NULL; |
| 331 | } |
| 332 | |
| 333 | return firstai; |
| 334 | } |
Show 90 lines (not cited by the judge)
| 257 | struct Curl_addrinfo * |
| 258 | Curl_he2ai(const struct hostent *he, int port) |
| 259 | { |
| 260 | struct Curl_addrinfo *ai; |
| 261 | struct Curl_addrinfo *prevai = NULL; |
| 262 | struct Curl_addrinfo *firstai = NULL; |
| 263 | struct sockaddr_in *addr; |
| 264 | #ifdef USE_IPV6 |
| 265 | struct sockaddr_in6 *addr6; |
| 266 | #endif |
| 267 | CURLcode result = CURLE_OK; |
| 268 | int i; |
| 269 | char *curr; |
| 270 | |
| 271 | if(!he) |
| 272 | /* no input == no output! */ |
| 273 | return NULL; |
| 274 | |
| 275 | DEBUGASSERT((he->h_name != NULL) && (he->h_addr_list != NULL)); |
| 276 | |
| 277 | for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { |
| 278 | size_t ss_size; |
| 279 | size_t namelen = strlen(he->h_name) + 1; /* include null-terminator */ |
| 280 | #ifdef USE_IPV6 |
| 281 | if(he->h_addrtype == AF_INET6) |
| 282 | ss_size = sizeof(struct sockaddr_in6); |
| 283 | else |
| 284 | #endif |
| 285 | ss_size = sizeof(struct sockaddr_in); |
| 286 | |
| 287 | /* allocate memory to hold the struct, the address and the name */ |
| 288 | ai = calloc(1, sizeof(struct Curl_addrinfo) + ss_size + namelen); |
| 289 | if(!ai) { |
| 290 | result = CURLE_OUT_OF_MEMORY; |
| 291 | break; |
| 292 | } |
| 293 | /* put the address after the struct */ |
| 294 | ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo)); |
| 295 | /* then put the name after the address */ |
| 296 | ai->ai_canonname = (char *)ai->ai_addr + ss_size; |
| 297 | memcpy(ai->ai_canonname, he->h_name, namelen); |
| 298 | |
| 299 | if(!firstai) |
| 300 | /* store the pointer we want to return from this function */ |
| 301 | firstai = ai; |
| 302 | |
| 303 | if(prevai) |
| 304 | /* make the previous entry point to this */ |
| 305 | prevai->ai_next = ai; |
| 306 | |
| 307 | ai->ai_family = he->h_addrtype; |
| 308 | |
| 309 | /* we return all names as STREAM, so when using this address for TFTP |
| 310 | the type must be ignored and conn->socktype be used instead! */ |
| 311 | ai->ai_socktype = SOCK_STREAM; |
| 312 | |
| 313 | ai->ai_addrlen = (curl_socklen_t)ss_size; |
| 314 | |
| 315 | /* leave the rest of the struct filled with zero */ |
| 316 | |
| 317 | switch(ai->ai_family) { |
| 318 | case AF_INET: |
| 319 | addr = (void *)ai->ai_addr; /* storage area for this info */ |
| 320 | |
| 321 | memcpy(&addr->sin_addr, curr, sizeof(struct in_addr)); |
| 322 | addr->sin_family = (CURL_SA_FAMILY_T)(he->h_addrtype); |
| 323 | addr->sin_port = htons((unsigned short)port); |
| 324 | break; |
| 325 | |
| 326 | #ifdef USE_IPV6 |
| 327 | case AF_INET6: |
| 328 | addr6 = (void *)ai->ai_addr; /* storage area for this info */ |
| 329 | |
| 330 | memcpy(&addr6->sin6_addr, curr, sizeof(struct in6_addr)); |
| 331 | addr6->sin6_family = (CURL_SA_FAMILY_T)(he->h_addrtype); |
| 332 | addr6->sin6_port = htons((unsigned short)port); |
| 333 | break; |
| 334 | #endif |
| 335 | } |
| 336 | |
| 337 | prevai = ai; |
| 338 | } |
| 339 | |
| 340 | if(result) { |
| 341 | Curl_freeaddrinfo(firstai); |
| 342 | firstai = NULL; |
| 343 | } |
| 344 | |
| 345 | return firstai; |
| 346 | } |
Show 2 lines (not cited by the judge)
| 1065 | #define curlx_calloc(nbelem,size) \ |
| 1066 | curl_dbg_calloc(nbelem, size, __LINE__, __FILE__) |
Show 1 lines (not cited by the judge)
| 1069 | #define curlx_free(ptr) curl_dbg_free(ptr, __LINE__, __FILE__) |