Free the previous nonce before storing a nextnonce - #341
Open
alvm wants to merge 2 commits into
Open
Conversation
added 2 commits
August 21, 2026 18:31
auc_digest_info() stores the server-supplied Authentication-Info nextnonce over cda->cda_ac->ac_nonce without releasing what was there: n = auth_get_params(home, info->ai_params, "nextnonce=", &nextnonce, NULL); if (n <= 0) return n; cda->cda_ac->ac_nonce = nextnonce; auth_get_params() allocates the new string from `home`, and the pointer being overwritten is owned by that same home: it came either from the challenge via auth_digest_challenge_get(), or from an earlier Authentication-Info through this same assignment. The consequence is not "one leak per nonce" but "all but the last": the next challenge calls auth_digest_challenge_free_params(), which does release ac_nonce - so of N responses carrying nextnonce between two challenges, N-1 strings are lost. A server that sends Authentication-Info on every 2xx therefore leaks one nonce string per response for as long as the credentials stay valid. Like every other object in this family the block is allocated from a su_home and stays reachable through the home's block table, so no leak checker reports it; it shows up only as heap growth proportional to the response rate. The free follows the discipline the rest of the file already uses - auth_digest_challenge_free_params() at auth_digest.c:126, and the cnonce path in auc_digest_challenge(), both su_free() from ca_home before replacing. su_free() ignores NULL, so the first nextnonce on a fresh client is unaffected.
Covers the free added to auc_digest_info(). The test drives the public client API - auc_challenge(), auc_credentials(), auc_info() - and asserts on the authenticator's own allocation statistics rather than on any internal pointer, because what is being tested is a lifetime and not a value. One Authentication-Info is fed first, so that whatever the first update allocates is already accounted for; the assertion is then that a hundred further updates leave the number of live blocks unchanged. Measured on this test: with the fix live blocks 0 -> 0 bytes 15 -> 15 without it live blocks 1 -> 101 bytes 22 -> 2222 One block per update, and 2 200 bytes for a hundred of them - 22 bytes each, which is exactly the nonce string in the fixture plus its terminator. Three things about the test worth stating, because all three are easy to get wrong: The statistics have to be taken from the authenticator's own home, not from the home handed to auc_challenge(). ca_create() makes the authenticator a su_home_clone() of that home, and su_home_get_stats() reports only the home it is given - it does not aggregate clones, regardless of its include_clones argument. Reading the parent shows nothing whether the nonce is freed or not. With the fix in place both block counts are zero, so an assertion on their equality alone would also hold if the statistics were not being collected at all. The test therefore checks first that the instrument reads something (hsb_bytes > 0), and then that the surviving nonce is the one the server sent last - a free of the wrong pointer, or a missing assignment, would satisfy the count while losing the value. And a leak checker cannot see this at all: the strings are allocated from a su_home and stay reachable through the home's block table until the home is destroyed, so valgrind and the sanitizers report nothing. The home's own statistics are the only instrument that sees it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
auc_digest_info()stores the server-suppliedAuthentication-Infonextnonce overcda->cda_ac->ac_noncewithout releasing what was there.auth_get_params()allocates the new string fromhome, and the pointer being overwritten is owned by that same home: it came either from the challenge viaauth_digest_challenge_get(), or from an earlierAuthentication-Infothrough this same assignment.The consequence is not "one leak per nonce" but all but the last: the next challenge calls
auth_digest_challenge_free_params(), which does releaseac_nonce— so of N responses carrying nextnonce between two challenges, N−1 strings are lost. A server that sendsAuthentication-Infoon every 2xx therefore leaks one nonce string per response for as long as the credentials stay valid.As with the companion PR #340 for the publication ETag, the block is allocated from a
su_homeand stays reachable through the home's block table, so no leak checker reports it; it shows up only as heap growth proportional to the response rate.The free follows the discipline the rest of the file already uses —
auth_digest_challenge_free_params()atauth_digest.c:126, and the cnonce path inauc_digest_challenge(), bothsu_free()fromca_homebefore replacing.su_free()ignores NULL, so the first nextnonce on a fresh client is unaffected.The test
The second commit adds a case to
test_auth_digest.c. It drives the public client API —auc_challenge(),auc_credentials(),auc_info()— and asserts on the authenticator's own allocation statistics rather than on any internal pointer, because what is being tested is a lifetime and not a value.One block per update, and 2 200 bytes for a hundred of them — 22 bytes each, which is exactly the nonce string in the fixture plus its terminator.
Three details in the test that are easy to get wrong, and are commented in place:
ca_create()makes it asu_home_clone()of the home passed toauc_challenge(), andsu_home_get_stats()reports only the home it is given — it does not aggregate clones, whatever itsinclude_clonesargument says. Reading the parent shows nothing either way.hsb_bytes > 0).auc_authorization_headers(). A free of the wrong pointer, or a missing assignment, would satisfy the block count while losing the value.Verification
Clean
debian:trixiecontainer (gcc 14.2.0, autoconf 2.72, automake 1.17, glibc 2.41),autogen.sh,configure, full build of libsofia:make checkThe failure is attributed rather than assumed — the test names its own assertion:
Warnings counted differentially with
-Wall -Wextraagainst the base:auth_client.c5 → 5 andtest_auth_digest.c5 → 5. The patch and its test add none.The fix has also been running in production on two instances for several days at the time of writing, against a registrar that sends
Authentication-Infoon every 2xx.