Skip to content

Preserve explicit crypto buffer clearing across backends - #576

Open
agamal026 wants to merge 14 commits into
nih-at:mainfrom
agamal026:fix/portable-crypto-clearing
Open

agamal026 wants to merge 14 commits into
nih-at:mainfrom
agamal026:fix/portable-crypto-clearing

Conversation

@agamal026

@agamal026 agamal026 commented Sep 6, 2026 •

Copy link
Copy Markdown

Summary

The build already probes for explicit_memset and explicit_bzero, but their results were missing from config.h.in. Propagate those results into the existing crypto-clearing backend selection.

Implement _zip_crypto_clear as a compiled internal function. It uses an explicit clearing API when available and volatile-qualified byte stores otherwise, so an ordinary dead-buffer memset cannot be optimized away. The function remains hidden from the public API. zipint.h no longer exposes <string.h>; source files include the string declarations they use directly.

The regression program uses one start/length helper that copies expected bytes, clears the requested range, and checks zero inside and unchanged bytes outside. It covers an interior range, zero length, and the full buffer. Because the symbol is hidden in shared builds, the test compiles the same implementation source directly.

AppVeyor installs nihtest with the Python 3.11 launcher already on its PATH, and test-enabled jobs fail if CTest discovers no tests. The first Windows run had compiled successfully while silently skipping regressions because unqualified py installed nihtest under Python 3.14.

Validation

  • GCC 15.2/OpenSSL focused crypto_clear.test: pass in native static, forced-fallback static, and native shared builds, using nihtest 1.11.1.
  • Native and fallback implementation/test syntax checks pass with -O3 -Wall -Wextra -Werror. The fallback object retains the byte stores at -O3.
  • After removing the transitive <string.h> include, targeted strict compiles exposed undeclared memset/memcpy in zip_algorithm_xz.c and undeclared memset in the zip_nonrandom.c regression helper. The current head adds direct includes there and in zip_crypto_openssl.c; all three affected sources pass focused declaration checks. This does not establish the cause of the Windows x86 failures.
  • clang-format 19 and diff whitespace checks pass.
  • AppVeyor build 1.0.1098 tested the previous head ac6ef11: x64 Windows ran 193 listed CTest cases with zero failures; x86 Windows passed crypto_clear.test but failed the two XZ conversion cases below. x64-UWP, x86-UWP, ARM64 Windows, and ARM64-UWP succeeded as compile-only lanes.
  • AppVeyor build 1.0.1099 tests the previous head b321748. Its x64 Windows test lane passed; x64-UWP failed during vcpkg zlib configuration and x86 Windows failed the two earlier XZ cases plus one LZMA case. The remaining lanes were still running or queued at 08:28 UTC. Build 1.0.1100 is for the intermediate 7224aae. Build 1.0.1101 is queued for the current head e346cfa; it has no completed Windows validation yet.

Windows CI gaps

  • The x86 Windows job uses MSVC 19.29.30159.0 and liblzma 5.8.1. The two XZ conversion failures repeat across the prior heads; build 1.0.1099 also saw set_compression_store_to_lzma.test exit with Windows fast-fail 0xC0000409. XZ 5.8.2's upstream change disables CLMUL CRC on older MSVC 32-bit x86 builds because optimized code can crash tests. The versions match that condition, but neither the XZ nor LZMA failures are causally attributed without a same-worker differential or crash subcode.
  • ARM Windows and ARM-UWP stop in nested vcpkg compiler-link checks before libzip compilation. The nested commands receive SDK system version 10.0.19041.0, yet the linker cannot find kernel32.lib or WindowsApp.lib, respectively. Unsuccessful SDK/triplet overrides were removed; no ARM worker fix is claimed.

No failing tests have been disabled, and a passing full CI matrix is not claimed. The explicit_memset backend has not been exercised here. This change strengthens the existing clearing primitive; it cannot guarantee erasure of every copy of sensitive data.

Implementation and review were AI-assisted.

@dillof

dillof commented Sep 16, 2026

Copy link
Copy Markdown
Member

Could you please look at my review comments?

Also, since you seem to know Appveyor, could you have a look at the two failing platforms? We would greatly appreciate any support on Windows.

@dillof dillof added the feedback Waiting for feedback from submitter. label Sep 16, 2026
@agamal026

agamal026 commented Sep 22, 2026 •

Copy link
Copy Markdown
Author

Thanks for the follow-up. I checked the PR conversation, review threads, and inline comments, but I cannot see your review comments yet. Could you link them or repost them so I can address the specific points?

For Windows CI, PR build 1.0.1075 has two failing x86 XZ tests; crypto_clear.test passes there. ARM and ARM-UWP stop before libzip compilation with MSB8037 because the Desktop C++ ARM component of Windows SDK 10.0.26100.0 is unavailable. The same ARM setup error appears on the later main build 1.0.1094, which points to the CI image/SDK rather than this patch, though I have not validated a fix. That main build's x86 job reported "No tests were found", so its green status is not a valid comparison for the two XZ failures. Their cause remains unconfirmed; I am investigating them separately.

Comment thread regress/programs/crypto_clear.c Outdated
}


static int check_zero_length(void) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create one function that takes start and length as arguments. Copy expected into buffer, call _zip_crypto_clear() and check that the cleared range is 0 and the rest is unchanged.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b321748. The single check_range(start, length) helper copies the expected bytes, clears the requested span, and compares every byte against zero inside or its original value outside. It covers an interior range, zero length, and the full buffer. The focused CTest passes in native static, forced-fallback static, and native shared builds.

Comment thread lib/zipint.h Outdated
#define ZIP_WANT_TORRENTZIP(za) ((za)->ch_flags & ZIP_AFL_WANT_TORRENTZIP)


#include <string.h>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to expose <string.h> unless we have to.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and addressed in b321748. zipint.h now declares the internal function without including <string.h>. The implementation includes it privately, and the source files that had relied on the transitive include now include the declarations they use directly. Local native and forced-fallback builds pass; Windows validation of this new head is pending.

Comment thread lib/zipint.h Outdated
static inline
#endif
void
_zip_crypto_clear(void *buffer, size_t length) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this function have to be inline?

I think volatile is needed so the compiler optimizer doesn't remove accesses to it. Why can't we call memset on the volatile pointer?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline was only needed for the old header-local fallback; b321748 moves clearing into a compiled internal function. memset accepts non-volatile void *, so passing a volatile pointer discards that qualifier, and casting it away does not prevent a dead-buffer memset from being optimized out. The fallback therefore keeps volatile byte stores; explicit_memset and explicit_bzero remain preferred where available. The internal symbol stays hidden, and the focused test compiles the same implementation directly. Local static and shared tests pass; Windows CI for this head is pending.

@dillof

dillof commented Sep 22, 2026

Copy link
Copy Markdown
Member

Thanks for the follow-up. I checked the PR conversation, review threads, and inline comments, but I cannot see your review comments yet. Could you link them or repost them so I can address the specific points?

Sorry, my bad, I missed that I had to press the submit button at the top. They should be visible now.

Thanks for investigating the CI build issues, we greatly appreciate it.

dillof added a commit that referenced this pull request Sep 22, 2026
Add defines for found functions in config.h

Use explicit for loop over volatile pointer in fallback routine so compiler can’t opimize the clearing away if the buffer isn’t used afterwards.

Based on PR #576.
@dillof

dillof commented Sep 22, 2026

Copy link
Copy Markdown
Member

We've merged your _zip_crypto_clear() fixes, thanks.

Your Appveyor commits seem like they add debug output. Can you run them on your branch? Or do we need to enable that, or should we commit them on the main branch? Thanks for looking into it.

@agamal026

Copy link
Copy Markdown
Author

Yes. The temporary ARM failure diagnostics ran on this branch in AppVeyor 1.0.1098. They showed vcpkg ARM test-link failures (kernel32.lib / WindowsApp.lib) before libzip compilation; they did not fix those jobs. I removed the diagnostic hook and unsuccessful ARM overrides in 3490217, so there is no debug output left to enable or merge. The current branch ran again in 1.0.1101: x64 passed 193 tests; x86 failed only the two XZ conversion tests; ARM and ARM-UWP still failed before libzip. The remaining AppVeyor test-discovery changes are already on main via b2da382. No further AppVeyor commit is needed from this branch.

@agamal026

Copy link
Copy Markdown
Author

Follow-up on the two x86 XZ failures: the controlled comparison now passes, with the same libzip b2da382, MSVC 19.29.30159.0 and 32-bit objects in all three variants:

liblzma build Same five regression tests
5.8.1, CLMUL CRC enabled Only the two XZ conversions fail, with the same 3221225477 / Compressed data invalid signatures as AppVeyor
5.8.1, CLMUL CRC disabled 5/5 pass
5.8.3, normal CLMUL option and upstream compiler workaround 5/5 pass

The five tests cover crypto clearing and both LZMA/XZ conversions; none are skipped. Source commit IDs, compiler metadata, generated test environments and JUnit output are retained in the run's x86-xz-comparison artifact. Two earlier harness setup failures remain documented on the diagnostic branch; they are not counted as comparison evidence.

This isolates the failing old-MSVC CLMUL path on this runner and matches XZ 5.8.2's documented compiler workaround. The vcpkg revision already used by your GitHub workflow contains liblzma 5.8.3, while AppVeyor 1.0.1108 used 5.8.1.

Updating AppVeyor's dependency snapshot is therefore a concrete fix to validate next. This comparison does not establish a full AppVeyor matrix fix or resolve the separate ARM SDK/linker failures. I have not changed the existing PR branch.

@agamal026

Copy link
Copy Markdown
Author

I pushed the AppVeyor dependency update and merged current main into the branch, keeping the integrated crypto implementation unchanged. The remaining PR diff is only seven added lines in appveyor.yml: use the same vcpkg snapshot as GitHub CI, which supplies liblzma 5.8.3.

The full AppVeyor build 1.0.1111 has finished:

  • x86 and x64 Windows each ran 193 CTest entries: 188 passed, five skipped, zero failed. Both XZ conversion tests and crypto_clear.test passed. The x86 log confirms MSVC 19.29.30159.0 and liblzma 5.8.3.
  • x86-UWP, x64-UWP, ARM64 Windows and ARM64-UWP built successfully; those jobs do not run tests.
  • The two ARM32 jobs still fail before libzip compilation and report MSB8037 for the missing Windows SDK 10.0.26100.0 Desktop C++ ARM components. Those setup failures remain unresolved, so the overall build is still red.

This validates the dependency update against the actual x86 regressions; it is not a full ARM32 fix. No matrix entries or test conditions were removed or disabled.

@agamal026

Copy link
Copy Markdown
Author

The ARM32 SDK fix is now validated on the actual AppVeyor workers. Build 1.0.1114, for head 5aeb57b, completed successfully across all eight original matrix jobs.

  • ARM Windows and ARM-UWP both initialize SDK 10.0.19041.0, complete the vcpkg dependency builds, and build libzip successfully. The logs confirm Desktop/UWP mode respectively and the same SDK selected by outer CMake.
  • x86 and x64 Windows each ran 193 CTest entries: 188 passed, five skipped, zero failed. Both XZ conversion tests and crypto_clear.test passed.
  • The other six rows are build-only, as before; their success does not represent runtime test coverage.

The remaining diff against current main is appveyor.yml plus four small ARM CI helper/triplet files. It retains the dependency snapshot update and selects the installed ARM SDK in both the outer build and vcpkg's compiler environment. Temporary diagnostics are removed; the integrated crypto implementation, eight matrix rows and test conditions are unchanged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feedback Waiting for feedback from submitter.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants