Files
httrack/tests/150_engine-strsprintf.test
Xavier Roche 2d041866ed An unauthenticated PROPFIND overflows the ProxyTrack DAV item buffer (#909)
* Bound the ProxyTrack DAV item buffer against an amplified PROPFIND path

proxytrack_add_DAV_Item() reserved a fixed 1024 bytes and then sprintf'd into
it unbounded. The request path lands in the response twice, once as the href
and once as the displayname, and escapexml() turns each '&' into '&', so
an unauthenticated PROPFIND of roughly 900 ampersands writes about 9000 bytes
off the end of the heap block. No cache entry and no Depth: 1 are needed.

Replace the hand-sized reserve with StringSprintf(), which measures the
formatted output and grows the String to fit, and convert the sibling sprintf
sites in the same file so no unbounded write into a String is left to
re-audit. Sizing beats clipping here: the String already owns a growable
buffer, so nothing has to be dropped.

Closes #836

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Bound StringSprintf's pre-C99 retry, and trim the review findings

A genuine vsnprintf conversion error returns -1 just as pre-C99 msvcrt does
for a short buffer, so the doubling search had no way to tell them apart and
grew until realloc aborted. Unreachable from these format strings, which use
only %s and %d, but the helper lives in a shared header and will get more
callers. Cap the search and empty the String past it.

Also: the count assertion piped into wc under pipefail, so a zero count killed
the test through set -e before its diagnostic could print.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Lift the NO_WEBDAV conditional out of a macro argument list

A preprocessor directive inside a macro invocation's arguments is undefined:
it was fine while this was a plain sprintf() call, and MSVC rejected it as
soon as it became StringSprintf(). GCC accepts it, so only the Windows leg
caught it. Compute the DAV header fragment first and pass it as an argument.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Cover StringSprintf's exact-fill case and the WebDAV enumeration branch

StringSprintf_ writes the terminator at buffer[ret], so widening its
`ret < capacity` guard by one byte is a heap overflow that only fires when the
formatted output exactly fills the capacity. No crawl test lands on a
capacity boundary, so the mutant survived the suite. The new `strsprintf`
self-test sweeps lengths around 256, 512, 1024 and 2048 with the String's
capacity pinned to each, plus a growing and shrinking sweep on one reused
String, and checks the length, the bytes and the terminator every time.

Test 147 only ever sent Depth: 0, leaving the enumeration branch the same PR
rewrote with no coverage at all. Its fixture gains a child directory, and a
Depth: 1 listing pins the item URLs, including the trailing '/' that
StringPopRight takes back off a directory name.

Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Declare the new WebDAV test in the Windows skip set

It skips on Windows for the same reason as its two neighbours, MSYS
cannot reap a background listener (#595), and the ratchet fails a skip
it was not told about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199wAkSVZNBNp51mpRkxMvv
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 10:37:22 +00:00

26 lines
690 B
Bash
Executable File

#!/bin/bash
#
# StringSprintf writes the terminator at buffer[ret], so a `ret <= capacity`
# guard overflows by one byte whenever the output exactly fills the capacity
# (#836). The proxytrack crawl tests never land on a capacity boundary.
set -euo pipefail
: "${top_srcdir:=..}"
# Resolve httrack before any cd: PATH may carry a build-relative entry.
bin=$(command -v httrack) || {
echo "FAIL: httrack not found on PATH" >&2
exit 1
}
fail() {
echo "FAIL: $1" >&2
exit 1
}
out=$("$bin" -#test=strsprintf) || fail "httrack -#test=strsprintf exited non-zero: $out"
test "$out" = "strsprintf self-test: OK" || fail "expected 'strsprintf self-test: OK', got: $out"
exit 0