mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
-comments
This commit is contained in:
@@ -23,13 +23,13 @@ make -C src/fuzz check \
|
||||
MHD_FUZZ_ITERATIONS="${FUZZ_ITERATIONS}" \
|
||||
MHD_FUZZ_SEED="${FUZZ_SEED}"
|
||||
|
||||
# Replay the whole committed seed corpus through every harness. This is
|
||||
# the part that proves a fixed crash stays fixed, and it is cheap.
|
||||
# Replay the whole committed seed corpus - including
|
||||
# corpus/known-findings/, the reproducers of src/fuzz/README section 6 -
|
||||
# through every harness. This is the part that proves a fixed crash
|
||||
# stays fixed, and it is cheap.
|
||||
#
|
||||
# corpus/known-findings/ is deliberately NOT included: check-corpus does
|
||||
# not recurse, and the reproducers of the findings that are still open
|
||||
# (src/fuzz/README section 6, patches/) are expected to fail on a build
|
||||
# with --enable-asserts. Replay those by hand when working on a fix:
|
||||
# for f in src/fuzz/corpus/known-findings/*.bin ; do
|
||||
# ./src/fuzz/fuzz_request --file="$f" ; done
|
||||
# It only means something because 1-build.sh configures --enable-asserts:
|
||||
# most of those reproducers trip an mhd_assert(), which compiles away
|
||||
# without it, so on a default build they would pass whether MHD is fixed
|
||||
# or not.
|
||||
make -C src/fuzz check-corpus
|
||||
|
||||
@@ -341,12 +341,14 @@ regression tests: ClusterFuzz keeps every seed in the corpus and replays
|
||||
it on every run.
|
||||
|
||||
Reproducers of findings that are still *open* are skipped. A finding is
|
||||
open exactly when `patches/$ID.diff` exists (see `patches/README`), and
|
||||
its reproducer crashes the target by construction, so shipping it would
|
||||
make every ClusterFuzz run open by rediscovering a bug that is already
|
||||
written down. Committing the fix means deleting the diff, and that
|
||||
alone promotes the reproducer to a shipped regression seed. At the time
|
||||
of writing this skips K7.
|
||||
open exactly when `patches/$ID.diff` exists, and its reproducer crashes
|
||||
the target by construction, so shipping it would make every ClusterFuzz
|
||||
run open by rediscovering a bug that is already written down. Committing
|
||||
the fix means deleting the diff, and that alone promotes the reproducer
|
||||
to a shipped regression seed.
|
||||
|
||||
Nothing is open at the time of writing, so `patches/` does not exist and
|
||||
all eight reproducers ship.
|
||||
|
||||
Regenerating the corpus from the harnesses' built-in seeds:
|
||||
|
||||
|
||||
@@ -64,14 +64,17 @@ for fuzzer in ${FUZZERS}; do
|
||||
|
||||
# The K* reproducers are fuzz_request inputs.
|
||||
#
|
||||
# Only the ones whose defect is already fixed are shipped. A finding
|
||||
# that is still open has an unapplied fix in patches/$ID.diff (see
|
||||
# patches/README), and its reproducer, by construction, crashes the
|
||||
# target: shipping it would make every ClusterFuzz run start by
|
||||
# Only the ones whose defect is already fixed are shipped. While a
|
||||
# finding is open its proposed fix is kept as an unapplied diff in
|
||||
# patches/$ID.diff, and its reproducer crashes the target by
|
||||
# construction; shipping it would make every ClusterFuzz run start by
|
||||
# rediscovering a bug that is already written down, and bury the
|
||||
# findings that are actually new. Deleting the diff -- which is what
|
||||
# committing the fix should do -- promotes the reproducer to a
|
||||
# permanent regression seed here, with no further edit.
|
||||
# findings that are actually new. Committing the fix deletes the diff,
|
||||
# and that alone promotes the reproducer to a permanent regression seed
|
||||
# here, with no further edit.
|
||||
#
|
||||
# patches/ therefore does not exist while nothing is open, which is the
|
||||
# normal state; the test below simply never fires then.
|
||||
if [ "${fuzzer}" = "fuzz_request" ] && [ -d "${FINDINGS}" ]; then
|
||||
for f in "${FINDINGS}"/*.bin; do
|
||||
[ -f "${f}" ] || continue
|
||||
|
||||
@@ -188,11 +188,12 @@ make -C src/fuzz check-corpus
|
||||
* `make check` in `src/fuzz` runs the full discipline / memory-limit
|
||||
range (`MHD_FUZZ_MIN_DISCIPLINE=-3`, `MHD_FUZZ_MIN_MEM_LIMIT=0`) with
|
||||
50000 iterations per harness, roughly 3 seconds in total under
|
||||
ASAN+UBSAN. The findings K1-K6 of `README` section 6 are all fixed on
|
||||
ASAN+UBSAN. The findings K1-K8 of `README` section 6 are all fixed on
|
||||
master, so the full range is clean; their reproducers stay in
|
||||
`corpus/known-findings/` as regressions. K7 is open, and its
|
||||
reproducer lives there too — `check-corpus` does not recurse into that
|
||||
directory, which is what keeps `make check` green while it is.
|
||||
`corpus/known-findings/`, and `make check-corpus` replays that
|
||||
directory alongside the generated corpus. Both only mean anything on a
|
||||
tree configured with `--enable-asserts`: most of those reproducers trip
|
||||
an `mhd_assert()`, which compiles away without it.
|
||||
|
||||
---
|
||||
|
||||
@@ -257,5 +258,5 @@ Two consequences for anyone editing `src/fuzz/`:
|
||||
`contrib/oss-fuzz/` also relies on two things this directory provides:
|
||||
`make -C src/fuzz refresh-corpus` (to regenerate `corpus/`) and the
|
||||
`corpus/known-findings/` reproducers, which it packages into
|
||||
`fuzz_request_seed_corpus.zip` so that K1–K6 become permanent
|
||||
`fuzz_request_seed_corpus.zip` so that they become permanent
|
||||
regressions. It is deliberately **not** part of `contrib/ci/jobs/`.
|
||||
|
||||
+20
-3
@@ -115,10 +115,27 @@ refresh-corpus: $(check_PROGRAMS)
|
||||
|
||||
# Replay the whole on-disk corpus through every harness; this is what a
|
||||
# CI regression run should do after a crash has been fixed.
|
||||
#
|
||||
# corpus/known-findings/ is listed separately because --corpus-dir does
|
||||
# not recurse. It holds the reproducers of the findings in README
|
||||
# section 6; all of them are fixed, so anything failing there is a
|
||||
# regression. The one intended exception is a reproducer committed while
|
||||
# its finding is still open: that fails here until the fix lands, which
|
||||
# is what a regression test for an unfixed bug is supposed to do.
|
||||
#
|
||||
# Note that these only bite on a tree configured with --enable-asserts;
|
||||
# without it mhd_assert() compiles away and most of these reproducers
|
||||
# pass whether MHD is fixed or not.
|
||||
CORPUS_DIRS = \
|
||||
$(srcdir)/corpus \
|
||||
$(srcdir)/corpus/known-findings
|
||||
|
||||
.PHONY: check-corpus
|
||||
check-corpus: $(check_PROGRAMS)
|
||||
for p in $(check_PROGRAMS) ; do \
|
||||
MHD_FUZZ_MIN_DISCIPLINE="$(MHD_FUZZ_MIN_DISCIPLINE)" \
|
||||
MHD_FUZZ_MIN_MEM_LIMIT="$(MHD_FUZZ_MIN_MEM_LIMIT)" \
|
||||
./$$p --corpus-dir=$(srcdir)/corpus || exit 1 ; \
|
||||
for d in $(CORPUS_DIRS) ; do \
|
||||
MHD_FUZZ_MIN_DISCIPLINE="$(MHD_FUZZ_MIN_DISCIPLINE)" \
|
||||
MHD_FUZZ_MIN_MEM_LIMIT="$(MHD_FUZZ_MIN_MEM_LIMIT)" \
|
||||
./$$p --corpus-dir=$$d || exit 1 ; \
|
||||
done ; \
|
||||
done
|
||||
|
||||
+42
-22
@@ -202,11 +202,13 @@ violating either makes MHD abort on input that is perfectly legal:
|
||||
header naming the upgrade token and not "close"). All three come
|
||||
off the wire, so the path stays attacker-driven.
|
||||
|
||||
Unlike the suspend rule, this one is not sufficient, and is not
|
||||
supposed to be: it is exactly the check a real application can
|
||||
perform, and a request that passes it can still abort MHD. That is
|
||||
finding K7 in section 6. Do not tighten it further to keep the
|
||||
suite green -- the point is that an application cannot do better.
|
||||
This is exactly the check a real application can perform, and no
|
||||
more. It deliberately does not try to predict whether MHD will
|
||||
accept the upgrade: a request that passes it used to abort MHD, which
|
||||
is finding K7 in section 6, and MHD_queue_response() now answers
|
||||
MHD_NO for that case instead. Do not tighten the condition to keep
|
||||
the suite green -- an application cannot do better than this, so
|
||||
neither should the harness.
|
||||
|
||||
The message-framing headers (Content-Length, Transfer-Encoding) are
|
||||
deliberately absent from the response-header table: MHD generates them
|
||||
@@ -529,7 +531,8 @@ K6 digestauth.c:860 check_nonce_nc():
|
||||
K7 connection.c:2596 build_header_response():
|
||||
mhd_assert ((NULL == r->upgrade_handler) ||
|
||||
(MHD_CONN_MUST_UPGRADE == c->keepalive))
|
||||
Open. Driven entirely from the wire, with default daemon options.
|
||||
Fixed by commit acef58a0. Was driven entirely from the wire, with
|
||||
default daemon options.
|
||||
|
||||
While parsing the request headers, connection.c sets
|
||||
c->keepalive = MHD_CONN_MUST_CLOSE
|
||||
@@ -565,9 +568,10 @@ K7 connection.c:2596 build_header_response():
|
||||
default-configured server, which is a good deal worse than the
|
||||
"application misuse" it first looked like.
|
||||
|
||||
Fix: patches/K7.diff, which rejects the response in
|
||||
MHD_queue_response() next to the other upgrade preconditions, so the
|
||||
application gets MHD_NO instead of an abort.
|
||||
The fix rejects the response in MHD_queue_response()
|
||||
(connection.c:8338) next to the other upgrade preconditions, so the
|
||||
application gets MHD_NO -- which it already has to handle -- instead
|
||||
of an abort.
|
||||
Repro: corpus/known-findings/K7-upgrade-after-must-close.bin
|
||||
GET / HTTP/1.1
|
||||
Host: x
|
||||
@@ -580,8 +584,9 @@ K7 connection.c:2596 build_header_response():
|
||||
K8 digestauth.c MHD_digest_auth_check_digest2():
|
||||
MHD_PANIC ("Wrong 'malgo3' value, only one base hashing
|
||||
algorithm ... must be specified, API violation")
|
||||
Open. An API-contract defect rather than a parsing bug: unlike K7
|
||||
the offending argument comes from the application, not the network.
|
||||
Fixed by commit 07c051dd. An API-contract defect rather than a
|
||||
parsing bug: unlike K7 the offending argument came from the
|
||||
application, not from the network.
|
||||
|
||||
MHD_DIGEST_ALG_AUTO is a documented member of
|
||||
enum MHD_DigestAuthAlgorithm, and the `algo` parameter of
|
||||
@@ -605,26 +610,41 @@ K8 digestauth.c MHD_digest_auth_check_digest2():
|
||||
because the digest length does not identify the algorithm (SHA-256
|
||||
and SHA-512/256 digests are both 32 bytes).
|
||||
|
||||
Fix: patches/K8.diff, which documents the restriction in
|
||||
microhttpd.h and returns MHD_NO instead of aborting.
|
||||
The fix documents the restriction in microhttpd.h and returns MHD_NO
|
||||
instead of aborting. AUTO is left unusable with the
|
||||
pre-computed-digest entry points, deliberately: there is nothing to
|
||||
map it to.
|
||||
Repro: none committed. The argument is the application's, so the
|
||||
harness never passes AUTO there (see run_digest_check() in
|
||||
fuzz_request.c); to see it, change the MHD_DIGEST_ALG_* argument of
|
||||
the variant-4 call to MHD_DIGEST_ALG_AUTO and replay
|
||||
corpus/fuzz_request-36.bin.
|
||||
corpus/fuzz_request-37.bin.
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
K1-K5 are fixed on master by commits 300a2ab0, 68c83f22, e04eb218,
|
||||
0b750975 and 6fcdfd43 respectively; K6 by f438804c. Their seven
|
||||
reproducers in `corpus/known-findings/` therefore replay clean, and
|
||||
`make check-corpus` asserts exactly that.
|
||||
All eight findings are fixed on master:
|
||||
|
||||
K7 and K8 are open, and their fixes are unapplied diffs in
|
||||
`../../patches/`. K7 has a reproducer, which aborts on an
|
||||
`--enable-asserts` build and passes with patches/K7.diff applied; K8 has
|
||||
none, because its trigger is an argument the application chooses.
|
||||
K1 300a2ab0 K4a 0b750975 K7 acef58a0
|
||||
K2 68c83f22 K4b 0b750975 K8 07c051dd
|
||||
K3 e04eb218 K5 6fcdfd43
|
||||
K6 f438804c
|
||||
|
||||
The eight reproducers in `corpus/known-findings/` therefore all replay
|
||||
clean on a build configured with `--enable-asserts`, and
|
||||
`make check-corpus` asserts exactly that -- it replays that directory
|
||||
along with the generated corpus. K8 has no reproducer, because its
|
||||
trigger is an argument the application chooses rather than anything that
|
||||
comes off the wire.
|
||||
|
||||
When the next finding is opened, its reproducer goes into
|
||||
`corpus/known-findings/` and will make `make check-corpus` fail until the
|
||||
fix lands. That is the intended behaviour: it is the same arrangement as
|
||||
the `XFAIL_TESTS` entries in `src/microhttpd/Makefile.am`, except that
|
||||
here the expectation is not encoded, so the commit that adds a live
|
||||
reproducer should say so. `contrib/oss-fuzz/make_seed_corpus.sh` has to
|
||||
skip such a reproducer as well, or every ClusterFuzz run starts by
|
||||
rediscovering it; see the comment there.
|
||||
|
||||
The `make check` defaults in Makefile.am
|
||||
|
||||
|
||||
+18
-10
@@ -132,13 +132,14 @@ known-findings/
|
||||
---------------
|
||||
|
||||
Reproducers for the issues that this suite found in MHD itself; see
|
||||
section 6 of ../README, which records the status of each. K1-K6 are
|
||||
fixed on master and therefore replay clean; K7 is open and *aborts* on a
|
||||
build configured with --enable-asserts.
|
||||
section 6 of ../README, which records the status of each. All of them
|
||||
are fixed on master, so all of them replay clean, and `check-corpus`
|
||||
replays this directory too.
|
||||
|
||||
They are kept out of the main corpus directory for exactly that reason:
|
||||
`check-corpus` does not recurse, so it stays green while an open finding
|
||||
still has a live reproducer here. Replay one with
|
||||
They are kept as a separate, explicitly named set rather than being
|
||||
folded into the main corpus because the files there are *generated*: a
|
||||
hand-written input would be clobbered or renumbered by the next
|
||||
`refresh-corpus`. Replay one with
|
||||
|
||||
./fuzz_request --file=src/fuzz/corpus/known-findings/K1-digest-empty-realm.bin
|
||||
|
||||
@@ -156,17 +157,24 @@ still has a live reproducer here. Replay one with
|
||||
K6-nonce-length-collision.bin digestauth.c check_nonce_nc()
|
||||
mhd_assert (0 == nn->nonce[noncelen])
|
||||
K7-upgrade-after-must-close.bin connection.c build_header_response()
|
||||
mhd_assert (upgrade -> MUST_UPGRADE)
|
||||
*** still open, see ../../../patches ***
|
||||
mhd_assert (upgrade -> MUST_UPGRADE);
|
||||
a single request carrying both
|
||||
Content-Length and chunked
|
||||
Transfer-Encoding, then an upgrade
|
||||
|
||||
These files are *not* regenerated by `--write-corpus`; they are edited by
|
||||
hand. When the input format changes they have to be migrated, and the
|
||||
migration has to be checked -- replay each one with `--verbose` before
|
||||
and after and confirm the daemon, handler, body and challenge counts are
|
||||
unchanged. The last such change made the API-selection block
|
||||
unconditional, which is six zero bytes inserted at offset 4 for all
|
||||
seven.
|
||||
unconditional, which is six zero bytes inserted at offset 4 for K1-K6.
|
||||
|
||||
K8 has no reproducer here: its trigger is an argument the application
|
||||
chooses, not network input, so the harness never performs it. See
|
||||
section 6 of ../README for how to reach it by hand.
|
||||
|
||||
A reproducer added while its finding is still open will make
|
||||
`make check-corpus` fail, which is intended -- that is what a regression
|
||||
test for an unfixed bug does. `contrib/oss-fuzz/make_seed_corpus.sh`
|
||||
skips such a file so that ClusterFuzz does not spend every run
|
||||
rediscovering it; the rule it uses is whether `patches/$ID.diff` exists.
|
||||
|
||||
+10
-10
@@ -1518,18 +1518,18 @@ ahc (void *cls,
|
||||
application performs, and all three inputs come off the wire, so
|
||||
the path stays fully attacker-driven.
|
||||
|
||||
It is deliberately NOT enough to keep MHD's
|
||||
It is deliberately NOT enough to predict whether MHD will accept
|
||||
the upgrade, and it is not meant to be: MHD may have decided during
|
||||
header parsing that the connection must close, and does not expose
|
||||
that decision. That used to trip
|
||||
mhd_assert (NULL == r->upgrade_handler ||
|
||||
MHD_CONN_MUST_UPGRADE == c->keepalive)
|
||||
in build_header_response() satisfied, and it is not meant to be:
|
||||
MHD may have decided during header parsing that the connection must
|
||||
close, and does not expose that decision. That is finding K7 (see
|
||||
README section 6); corpus/known-findings/K7-upgrade-after-must-close.bin
|
||||
is the reproducer, and patches/K7.diff turns the abort into the
|
||||
MHD_NO that the code below already handles. Do not "fix" this by
|
||||
weakening the condition here -- the check above is what an
|
||||
application can actually do, and the harness has to behave like
|
||||
one. */
|
||||
in build_header_response() -- finding K7, see README section 6,
|
||||
reproducer corpus/known-findings/K7-upgrade-after-must-close.bin --
|
||||
and MHD_queue_response() now returns MHD_NO for it instead, which
|
||||
the code below already handles. Do not "fix" this by tightening
|
||||
the condition here: the check above is what an application can
|
||||
actually do, and the harness has to behave like one. */
|
||||
if (cfg.allow_upgrade &&
|
||||
(NULL != version) &&
|
||||
(0 == strcmp (version, MHD_HTTP_VERSION_1_1)) &&
|
||||
|
||||
Reference in New Issue
Block a user