<feed xmlns='http://www.w3.org/2005/Atom'>
<title>uhttpd, branch master</title>
<subtitle>Tiny HTTP server</subtitle>
<id>https://git-03.infra.openwrt.org/project/uhttpd/atom?h=master</id>
<link rel='self' href='https://git-03.infra.openwrt.org/project/uhttpd/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/'/>
<updated>2026-05-15T00:54:17Z</updated>
<entry>
<title>auth: replace strcmp with constant-time password comparison</title>
<updated>2026-05-15T00:54:17Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-03-31T20:58:00Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=6fadf0da50509a510ac4f85d2adb70a83de2e1fc'/>
<id>urn:sha1:6fadf0da50509a510ac4f85d2adb70a83de2e1fc</id>
<content type='text'>
strcmp short-circuits on the first differing byte, leaking password
match progress via measurable response time differences. Add
uh_pass_compare() which XORs all bytes unconditionally and only
returns true when both length and content match, preventing a
timing-based password oracle attack.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>auth: do not accept stored crypt hash as plaintext password</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-03T20:54:03Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=b33ca5d377189f0fa7f6ee1b325ec08078a64c91'/>
<id>urn:sha1:b33ca5d377189f0fa7f6ee1b325ec08078a64c91</id>
<content type='text'>
uh_auth_check first compared the supplied password to the stored
credential with strcmp() and only fell through to crypt() on
mismatch. The strcmp branch is meant to support plaintext
credentials in /etc/httpd.conf, but it also matched when the stored
credential is a crypt hash and the request supplies that exact hash
as the password.

When the password line uses the '$p$&lt;user&gt;' indirection, realm-&gt;pass
is populated from /etc/shadow or /etc/passwd. Anyone who manages to
read the hash (a backup, an unrelated disclosure bug, an offline
copy) can therefore authenticate by sending the hash itself,
bypassing the need to recover the underlying password.

Skip the plaintext compare when realm-&gt;pass starts with '$', which
is the marker for every modular crypt(3) format (md5crypt, sha256,
sha512, bcrypt, yescrypt, ...). Plaintext credentials configured
via httpd.conf still work; stored hashes are only honored through
crypt().

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>client: parse chunked transfer chunk size safely</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-03-31T20:56:47Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=9404e6c62bb7dc452a294a62c33eb18beb9ef433'/>
<id>urn:sha1:9404e6c62bb7dc452a294a62c33eb18beb9ef433</id>
<content type='text'>
The chunk-size hex field is parsed into content_length (int). The
previous strtoul could wrap negative-looking inputs into huge
positive values; switching to strtol exposes negatives but leaves
the 64-bit INT_MAX overflow window open, where a long value above
INT_MAX truncates implementation-defined when narrowed.

Parse into a long, check errno for strtol overflow, and reject any
chunk size above INT_MAX. On malformed or out-of-range input the
chunked transfer state is torn down as before.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>client: parse Content-Length safely</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-03-31T20:56:15Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=2c869c094c25e1b6b7c1ba786f22280a6c153447'/>
<id>urn:sha1:2c869c094c25e1b6b7c1ba786f22280a6c153447</id>
<content type='text'>
content_length is declared int but the previous strtoul cast silently
wrapped negative inputs into huge positive values, making the &lt; 0
guard unreachable. Switching to strtol exposes negatives, but on
64-bit platforms a value above INT_MAX still passes strtol's own
range check and truncates implementation-defined when narrowed to int.

Parse into a long, check errno for strtol overflow, and reject any
value above INT_MAX or below zero before assigning. The error path
returns 400 Bad Request as before.

Pull in errno.h and limits.h for the new checks.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>main: fix daemonization stdio redirection and fd leak</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-04-13T08:31:21Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=778ccbbf5f8aa90e368100897f59322314287ab4'/>
<id>urn:sha1:778ccbbf5f8aa90e368100897f59322314287ab4</id>
<content type='text'>
Two bugs in the daemon child setup:

1. The condition `cur_fd &gt; 0` fails to redirect stdin/stdout/stderr
   when open("/dev/null") returns fd 0 (possible if stdin was already
   closed before daemonising).  Change to `cur_fd &gt;= 0` to handle
   that case correctly.

2. After the three dup2() calls the original file descriptor
   `cur_fd` is never closed.  If open() returned a descriptor number
   greater than 2 (the common case), that descriptor is leaked into
   the server process for its entire lifetime.  Add `close(cur_fd)`
   when `cur_fd &gt; 2` to release it.

3. Change the open() flags from O_WRONLY to O_RDWR so that fd 0
   (stdin) is also opened in a readable state, matching normal daemon
   practice.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>utils: fix off-by-one out-of-bounds read in uh_b64decode</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-04-13T08:25:23Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=add5389470f0f5534a1b7ef79f69e98c95a5ba82'/>
<id>urn:sha1:add5389470f0f5534a1b7ef79f69e98c95a5ba82</id>
<content type='text'>
The loop condition used `i &lt;= slen`, which causes `str[slen]` to be
read after processing all `slen` bytes.  This is one byte past the
end of the declared input length and constitutes an out-of-bounds
read for any caller that passes a non-null-terminated buffer.

The existing call site in auth.c happens to pass a null-terminated C
string, so `str[slen]` equals '\0' and the loop terminates naturally
without memory corruption in practice.  However the function
signature advertises only `slen` bytes of valid input, so the read is
still incorrect.

Fix by changing the condition to `i &lt; slen`.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>utils: remove unreachable return statement in uh_addr_rfc1918</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-04-13T08:30:15Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=93432149a7aea18953c5f2d89a7c25c56342d5f9'/>
<id>urn:sha1:93432149a7aea18953c5f2d89a7c25c56342d5f9</id>
<content type='text'>
The function already returns the result of the range-check expression
on the preceding line.  The `return 0;` statement that follows is
dead code and will never be executed.  Remove it to eliminate the
compiler warning and avoid misleading readers.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>file: bail out of file_write_cb on read error</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-03T22:23:38Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=53e7150619a3662226d1de408c5ac51f859c47f2'/>
<id>urn:sha1:53e7150619a3662226d1de408c5ac51f859c47f2</id>
<content type='text'>
The static-file delivery loop in file_write_cb only handled the
EINTR branch of a failed read(); other failure modes (EIO on a
backing storage error, EBADF on an unexpectedly-closed fd, etc.)
fell through with r &lt; 0 and reached uh_chunk_write(cl, uh_buf, r),
which forwards the negative length to ustream_write() where it is
interpreted as an unsigned size. The result is a chunked transfer
of arbitrary memory adjacent to uh_buf to the client.

On any non-EINTR read error, terminate the request like we already
do for EOF instead of writing past the buffer.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>utils: fix one-byte overflow in uh_urldecode</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-03T22:22:00Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=ced7b15c346741982fe317cd2d9de0895e6c64a9'/>
<id>urn:sha1:ced7b15c346741982fe317cd2d9de0895e6c64a9</id>
<content type='text'>
The output buffer was filled up to blen bytes without writing a
trailing NUL, leaving every caller to either append one or rely on
a happenstance zero byte. The documented contract said "not
null-terminated" but every in-tree caller used the result as a C
string, making the missing NUL a latent bug.

Reserve one byte at the tail, write the NUL, and update the contract
in the comment. Reject blen &lt;= 0 as overflow.

Adjust the -d option's caller to pass the actual buffer size
(strlen + 1) rather than strlen -- the buffer was always sized for
the NUL, only the blen argument was off-by-one. Without this the -d
handler would reject every plain-ASCII input as "invalid encoding"
after the contract change.

Also cast ctype arguments to unsigned char to avoid undefined
behavior on signed-char platforms.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
<entry>
<title>file: distinguish parse failure from epoch in date precondition checks</title>
<updated>2026-05-15T00:29:10Z</updated>
<author>
<name>Hauke Mehrtens</name>
</author>
<published>2026-05-03T22:18:48Z</published>
<link rel='alternate' type='text/html' href='https://git-03.infra.openwrt.org/project/uhttpd/commit/?id=8e5b26f93798af44e77d91146d26ae0f95e63006'/>
<id>urn:sha1:8e5b26f93798af44e77d91146d26ae0f95e63006</id>
<content type='text'>
uh_file_date2unix returned 0 on strptime() failure, which the
callers could not distinguish from a successfully parsed
1970-01-01 00:00:00 UTC. Two of those callers misbehave:

  * uh_file_if_modified_since compared the parsed value against
    st_mtime with '&gt;='. For files whose mtime sits at the Unix
    epoch (some tmpfs / squashfs entries on OpenWrt initramfs),
    any malformed If-Modified-Since header satisfied 0 &gt;= 0 and
    a 304 with no body was returned, breaking content delivery.

  * uh_file_if_unmodified_since compared with '&lt;='. A malformed
    If-Unmodified-Since header always satisfies 0 &lt;= st_mtime
    (for any real file with mtime &gt; 0), so the precondition fired
    and every request received a 412 Precondition Failed.

Return (time_t)-1 from uh_file_date2unix on parse failure, and
ignore the precondition (treat the header as if absent) in the
two callers when that sentinel is returned.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
Signed-off-by: Hauke Mehrtens &lt;hauke@hauke-m.de&gt;
</content>
</entry>
</feed>
