From d63432d1f8e759f4c6c64a100fa307656883d0f3 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 26 Mar 2026 14:45:37 +0100 Subject: [PATCH] tool_cb_hdr: only truncate etags output when regular file When sending the output to stdout it cannot truncate. Add test1619 to verify --etag-save to stdout Spotted by Codex Security Closes #21103 --- src/tool_cb_hdr.c | 23 ++++++++++++--------- tests/data/Makefile.am | 4 ++-- tests/data/test1619 | 45 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 tests/data/test1619 diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index fc03cf143b..5e8611bb3c 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -263,18 +263,23 @@ static size_t save_etag(const char *etag_h, const char *endp, if(eot >= etag_h) { size_t etag_length = eot - etag_h + 1; - /* - * Truncate the etag save stream, it can have an existing etag value. - */ + curlx_struct_stat file; + int fd = fileno(etag_save->stream); + + if((fd != -1) && + !curlx_fstat(fd, &file) && + (S_ISREG(file.st_mode))) { + /* + * Truncate regular files to avoid stale etag content. + */ #ifdef HAVE_FTRUNCATE - if(ftruncate(fileno(etag_save->stream), 0)) { - return CURL_WRITEFUNC_ERROR; - } + if(ftruncate(fileno(etag_save->stream), 0)) + return CURL_WRITEFUNC_ERROR; #else - if(fseek(etag_save->stream, 0, SEEK_SET)) { - return CURL_WRITEFUNC_ERROR; - } + if(fseek(etag_save->stream, 0, SEEK_SET)) + return CURL_WRITEFUNC_ERROR; #endif + } fwrite(etag_h, 1, etag_length, etag_save->stream); /* terminate with newline */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 830ac2fada..44473ecfa3 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -213,8 +213,8 @@ test1580 test1581 test1582 test1583 test1584 test1585 test1586 \ test1590 test1591 test1592 test1593 test1594 test1595 test1596 test1597 \ test1598 test1599 test1600 test1601 test1602 test1603 test1604 test1605 \ test1606 test1607 test1608 test1609 test1610 test1611 test1612 test1613 \ -test1614 test1615 test1616 test1617 test1618 \ -test1620 test1621 test1622 test1623 test1624 test1625 test1626 test1627 \ +test1614 test1615 test1616 test1617 test1618 test1619 test1620 test1621 \ +test1622 test1623 test1624 test1625 test1626 test1627 \ \ test1630 test1631 test1632 test1633 test1634 test1635 test1636 test1637 \ test1638 \ diff --git a/tests/data/test1619 b/tests/data/test1619 new file mode 100644 index 0000000000..ba520af2e2 --- /dev/null +++ b/tests/data/test1619 @@ -0,0 +1,45 @@ + + + + +HTTP +HTTP GET + + + + + +HTTP/1.1 200 OK +ETag: W/"heyheyhey" +Content-Length: 4 + +yes + + + + + +http + + +--etag-save to stdout + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -O --etag-save - + + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +W/"heyheyhey" + + + +