✓ProveIt
Examples / Report #2236133 Saved analysis

CVE-2023-46219: HSTS long file name clears contents

curl · 2023-11-0217.4s analysis · $0.016 in model calls

What the report claims

cURL's HSTS database can be overwritten when a filename exceeds 243 bytes, causing loss of stored HSTS entries.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2023-46219 HSTS long filename clears contents

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

ProveIt's finding

Needs human review

The available evidence does not settle the report. A maintainer should investigate.

Judgment withdrawn. The answer failed the evidence check.
See the withdrawn model reasoning

The following reasoning failed the citation check and does not support the final finding.

  • Curl_fopen truncates the original file (line 59) before verifying the temporary filename can be created (line 78).
  • If the temporary filename exceeds the filesystem limit (e.g., >255 bytes on ext4), open() fails but the original file is already zeroed.
  • The HSTS database file is user-controlled via --hsts, so a user (or attacker controlling command line) can trigger this data loss.
  • This is not listed in curl's 'Not security issues' policy sections; it causes silent data loss of security-relevant HSTS entries.
How this analysis ran 5 claims · 14 citations verified

Evidence check: judge quoted an external source that does not say that (https://docs.rs/rsext4/latest/src/rsext4/entries/dir_entry.rs.html)

Checked against curl master on 2023-11-02 (ddb95c155e18). Test 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 · 5 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 2955 tokens · $0.0003
3 · Evidence
4 code snippets
real curl source at ddb95c155e18 · 5 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
9896 tokens · 10.3s · $0.0159
5 · Receipts
14 of 14 citations verified
every citation must point at code the judge was shown
Inspect the withdrawn draft reply

This draft belongs to the withdrawn judgment. It may contain unsupported claims.

Thank you for the detailed report and reproduction. The flaw is confirmed: Curl_fopen in lib/fopen.c opens the original file for writing (truncating it) at line 59 before creating the temporary file at line 71. If the temp filename exceeds the filesystem limit (e.g., 255 bytes on ext4), the open() at line 78 fails with ENAMETOOLONG, but the original HSTS file has already been emptied. This causes silent loss of all HSTS entries. We will fix this by creating the temporary file first, or by checking the filename length upfront.

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

This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.

Report claims

C1flaw supported

cURL creates temporary files with names potentially exceeding the filesystem's maximum filename length, leading to HSTS database overwrite when the name is longer than 243 bytes.

Read the reasoning

The code opens the original file for writing (truncating it) at line 59 before attempting to create the temporary file at line 71. If the temporary filename exceeds the filesystem's maximum name length (typically 255 bytes), the open() at line 78 fails, but the original file has already been truncated to zero bytes.

C2location supported

The flaw occurs in the HSTS database handling code that generates temporary filenames using a random suffix.

Read the reasoning

The temporary filename generation in Curl_fopen (lib/fopen.c) uses a 9-byte buffer for the random suffix (8 chars + NUL) and appends '.tmp' (4 chars) plus a dot separator, adding 13 bytes to the original filename.

C3trigger supported

An attacker (or user) provides a filename longer than 243 bytes for the HSTS database, causing the temporary filename to exceed the filesystem limit.

Read the reasoning

The filename comes from the caller (HSTS code in lib/hsts.c, which gets it from the user via --hsts). If the user supplies a filename >242 bytes (255 - 13), the temp name exceeds 255 bytes and open() fails with ENAMETOOLONG.

C4impact supported

The HSTS database file is overwritten with an empty file, resulting in loss of all stored HSTS entries.

Read the reasoning

When temp file creation fails, Curl_fopen returns an error (line 109) but the original file was already truncated at line 59 and closed at line 64. The caller (hsts.c) receives the error and does not rename anything, leaving the original file as a zero-byte file.

C5reproduction supported

Create an HSTS file with a name longer than 243 bytes, then use curl --hsts <thatfile> to fetch a URL; the database will be cleared.

Read the reasoning

The reproduction steps match the code path: user provides long filename via --hsts, HSTS code calls Curl_fopen, which truncates the file then fails to create temp file due to name length, leaving the database empty.

What the report says about other projects

confirmed The maximum filename length on ext4 is typically 255 bytes.

“Maximum filename length in bytes (keeps total paths well under the 255-byte component limit common to ext4/APFS/NTFS).” source

Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: I've discovered a significant security flaw in cURL's file handling, particularly affecting the HSTS (HTTP Strict Transport Security) database when handling long filenames. ### Vulnerability Description cURL erroneously creates temporary files with names potentially exceeding the filesystem's maximum filename length (typically 255 bytes for ext4, etc.). If a filename used in the HSTS database is longer than 243 bytes (255 bytes minus 9 for the random suffix and 4 for the '.tmp' extension), an unexpected security error occurs, leading to the HSTS database being overwritten. ### Affected Code **File: curl/lib/fopen.c** ```c CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, FILE **fh, char **tempname) { CURLcode result = CURLE_WRITE_ERROR; unsigned char randsuffix[9]; // Random suffix generation <======= ... tempstore = aprintf("%s.%s.tmp", filename, randsuffix); // Temporary filename creation <======= if(!tempstore) { result = CURLE_OUT_OF_MEMORY; goto fail; } } ``` **File: curl/lib/hsts.c** ```c result = Curl_fopen(data, file, &out, &tempstore); ... if(!result && tempstore && Curl_rename(tempstore, file)) // Attempt to rename temp file to actual file <====== result = CURLE_WRITE_ERROR; if(result && tempstore) unlink(tempstore); // Remove temp file if error occurs ``` ## Steps To Reproduce: First let’s check the correct behaviour. I’ve created simple hsts file for cxsecurity.com domain ```bash $ cat ok.hsts.txt # Your HSTS cache. https://curl.se/docs/hsts.html # This file was generated by libcurl! Edit at your own risk. cxsecurity.com "20241031 12:12:12" $ curl --hsts ok.hsts.txt http://cxsecurity.com -v * Switched from HTTP to HTTPS due to HSTS => https://cxsecurity.com/ * Trying 188.114.97.1:443... …
 ``` So works great. Let’s try update the database and add Facebook ```bash $ curl --hsts ok.hsts.txt https://facebook.com -v * Trying 31… * Connected to facebook.com … … < Strict-Transport-Security: max-age=15552000; preload … $ cat ok.hsts.txt # Your HSTS …

Source code

lib/fopen.clines 49–110 · definition of Curl_fopen
49CURLcode Curl_fopen(struct Curl_easy *data, const char *filename,
C3: Curl_fopen takes filename from caller
50 FILE **fh, char **tempname)
51{
52 CURLcode result = CURLE_WRITE_ERROR;
53 unsigned char randsuffix[9];
C2: randsuffix[9] buffer for 8 alphanumeric chars + NUL
54 char *tempstore = NULL;
55 struct_stat sb;
56 int fd = -1;
57 *tempname = NULL;
58
59 *fh = fopen(filename, FOPEN_WRITETEXT);
C1: Opens original file for writing (FOPEN_WRITETEXT), which truncates it to zero length
C4: fopen(filename, FOPEN_WRITETEXT) truncates original file
C5: Truncation happens on any Curl_fopen call with a regular file
60 if(!*fh)
61 goto fail;
62 if(fstat(fileno(*fh), &sb) == -1 || !S_ISREG(sb.st_mode))
63 return CURLE_OK;
64 fclose(*fh);
C4: fclose(*fh) after truncation
65 *fh = NULL;
66
67 result = Curl_rand_alnum(data, randsuffix, sizeof(randsuffix));
C2: Curl_rand_alnum fills randsuffix
68 if(result)
69 goto fail;
70
71 tempstore = aprintf("%s.%s.tmp", filename, randsuffix);
C1: Creates temp filename: filename + '.' + 8-char randsuffix + '.tmp' (13 extra chars)
C2: aprintf formats temp name as "%s.%s.tmp"
C3: Temp name length = strlen(filename) + 13
72 if(!tempstore) {
73 result = CURLE_OUT_OF_MEMORY;
74 goto fail;
75 }
76
77 result = CURLE_WRITE_ERROR;
78 fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, 0600);
C1: open() on tempstore fails with ENAMETOOLONG if total length exceeds filesystem limit (e.g., 255 bytes on ext4)
C3: open() fails if temp name exceeds filesystem limit
C5: Failure triggered by long filename
79 if(fd == -1)
80 goto fail;
81
82#ifdef HAVE_FCHMOD
83 {
84 struct_stat nsb;
85 if((fstat(fd, &nsb) != -1) &&
86 (nsb.st_uid == sb.st_uid) && (nsb.st_gid == sb.st_gid)) {
87 /* if the user and group are the same, clone the original mode */
88 if(fchmod(fd, (mode_t)sb.st_mode) == -1)
89 goto fail;
90 }
91 }
92#endif
93
94 *fh = fdopen(fd, FOPEN_WRITETEXT);
95 if(!*fh)
96 goto fail;
97
98 *tempname = tempstore;
99 return CURLE_OK;
100
101fail:
C4: fail path returns error without restoring original file
102 if(fd != -1) {
103 close(fd);
104 unlink(tempstore);
105 }
106
107 free(tempstore);
108
109 return result;
110}
lib/curl_setup.hlines 390–390 · definition of fopen
390# define fopen(fname,mode) curlx_win32_fopen(fname, mode)
lib/rename.clines 41–71 · definition of Curl_rename
Show 31 lines (not cited by the judge)
41int Curl_rename(const char *oldpath, const char *newpath)
42{
43#ifdef WIN32
44 /* rename() on Windows doesn't overwrite, so we can't use it here.
45 MoveFileEx() will overwrite and is usually atomic, however it fails
46 when there are open handles to the file. */
47 const int max_wait_ms = 1000;
48 struct curltime start = Curl_now();
49 TCHAR *tchar_oldpath = curlx_convert_UTF8_to_tchar((char *)oldpath);
50 TCHAR *tchar_newpath = curlx_convert_UTF8_to_tchar((char *)newpath);
51 for(;;) {
52 timediff_t diff;
53 if(MoveFileEx(tchar_oldpath, tchar_newpath, MOVEFILE_REPLACE_EXISTING)) {
54 curlx_unicodefree(tchar_oldpath);
55 curlx_unicodefree(tchar_newpath);
56 break;
57 }
58 diff = Curl_timediff(Curl_now(), start);
59 if(diff < 0 || diff > max_wait_ms) {
60 curlx_unicodefree(tchar_oldpath);
61 curlx_unicodefree(tchar_newpath);
62 return 1;
63 }
64 Sleep(1);
65 }
66#else
67 if(rename(oldpath, newpath))
68 return 1;
69#endif
70 return 0;
71}
lib/curl_printf.hlines 49–49 · definition of aprintf
Show 1 lines (not cited by the judge)
49# define aprintf curl_maprintf