diff --git a/CLAUDE.md b/CLAUDE.md index ea09d36..acd3782 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,7 +100,8 @@ independent implementations (C#, Go, Rust, TypeScript). - Build: CMake. `cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallel`, then `ctest --test-dir build --output-on-failure` runs the suite (51 tests). The `netcode_test` target compiles netcode.c into itself with `NETCODE_ENABLE_TESTS`, so it - links only sodium. `-DNETCODE_SANITIZE=ON` adds ASan+UBSan (sodium gets ASan only); + links only sodium. `-DNETCODE_SANITIZE=ON` adds ASan+UBSan (sodium keeps UBSan + except alignment); `-DNETCODE_FUZZ=ON` builds the `fuzz/` harnesses (libFuzzer where available, else a standalone file replayer); `-DNETCODE_NONCE_AUDIT=ON` records the key and nonce of every packet the tests encrypt and fails the run on a repeat (test-only, nothing enters the diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b1dcf2..cae41c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,9 +81,9 @@ endif() include(GNUInstallDirs) -# sanitizers apply to the whole build. the vendored crypto is exempted from UBSan -# below (third-party SIMD code uses intentional type punning / unaligned access that -# UBSan flags but is not netcode's to fix); it still gets AddressSanitizer. +# sanitizers apply to the whole build. the vendored crypto is exempted only from +# UBSan alignment (SIMD kernels use unaligned loads and type punning). nonnull-attribute +# stays on so a NULL zero-length additional-data pointer is a real failure (netcode#186). if(NETCODE_SANITIZE) if(MSVC) @@ -125,7 +125,7 @@ else() -Wno-unused-variable -Wno-type-limits) if(NETCODE_SANITIZE) - target_compile_options(sodium PRIVATE -fno-sanitize=undefined) + target_compile_options(sodium PRIVATE -fno-sanitize=alignment) endif() endif() diff --git a/netcode.c b/netcode.c index 9245ca5..18f0008 100755 --- a/netcode.c +++ b/netcode.c @@ -6351,7 +6351,7 @@ static void test_connect_token() static void test_challenge_token() { - // generate a challenge token + // additional data is NULL, 0. sodium.c must keep nonnull-attribute so this path guards #186. struct netcode_challenge_token_t input_token; diff --git a/sodium/NOTES.md b/sodium/NOTES.md index 20a4d64..db0c524 100644 --- a/sodium/NOTES.md +++ b/sodium/NOTES.md @@ -62,6 +62,27 @@ Reviewing a new release means: ### Review log +- **1.0.22 attributes (2026-09-08).** Fourteen declarations carried a bare + `__attribute__ ((nonnull))`. This pass was the crypto slice netcode actually + calls (`crypto_stream_chacha20{,_ietf}_xor{,_ic}`, + `crypto_onetimeauth{,_poly1305}{,_verify,_update}`), because a NULL + additional-data pointer with length zero is a valid call and is how netcode + encrypts challenge tokens; the bare attribute aborts a UBSAN build on that + path. Those eleven declarations now match upstream 1.0.22, including + `crypto_aead_xchacha20poly1305_ietf_decrypt_detached` which had + `nonnull(3, 5, 9, 9)` (nonce missing, parameter 9 twice); upstream is + `nonnull(3, 5, 8, 9)`. Nine other divergences remain in the utils slice and + are not reachable from netcode's own code: `sodium_memcmp` and + `sodium_compare` keep a bare nonnull beside unused-result where upstream has + unused-result only; `sodium_bin2hex` and `sodium_bin2base64` keep bare + nonnull against upstream `nonnull(1)`; `sodium_hex2bin` and + `sodium_base642bin` carry `nonnull(1, 3)` against upstream `nonnull(1)`; + `sodium_memzero`, `sodium_add` and `sodium_sub` carry a bare nonnull where + upstream declares none. Header attributes only; crypto text unchanged. The + vendored sodium object's UBSan exemption is alignment only, not all of + undefined, so `test_challenge_token` (additional data NULL, 0) is a real + nonnull-attribute guard. See netcode#186. + - **1.0.22 (reviewed AND incorporated, 2026-07-25).** The vendored slice now carries the 1.0.22 text. Most of 1.0.21/1.0.22 is outside the slice — the ed25519 small-order-point fix, ipcrypt, XOF/SHA-3, ML-KEM768 / X-Wing and assorted build work do not touch the diff --git a/sodium/sodium.h b/sodium/sodium.h index 37c6590..98505ec 100644 --- a/sodium/sodium.h +++ b/sodium/sodium.h @@ -113,13 +113,13 @@ int crypto_stream_chacha20(unsigned char *c, unsigned long long clen, int crypto_stream_chacha20_xor(unsigned char *c, const unsigned char *m, unsigned long long mlen, const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); + __attribute__ ((nonnull(1, 4, 5))); int crypto_stream_chacha20_xor_ic(unsigned char *c, const unsigned char *m, unsigned long long mlen, const unsigned char *n, uint64_t ic, const unsigned char *k) - __attribute__ ((nonnull)); + __attribute__ ((nonnull(1, 4, 6))); void crypto_stream_chacha20_keygen(unsigned char k[crypto_stream_chacha20_KEYBYTES]) __attribute__ ((nonnull)); @@ -143,13 +143,13 @@ int crypto_stream_chacha20_ietf(unsigned char *c, unsigned long long clen, int crypto_stream_chacha20_ietf_xor(unsigned char *c, const unsigned char *m, unsigned long long mlen, const unsigned char *n, const unsigned char *k) - __attribute__ ((nonnull)); + __attribute__ ((nonnull(1, 4, 5))); int crypto_stream_chacha20_ietf_xor_ic(unsigned char *c, const unsigned char *m, unsigned long long mlen, const unsigned char *n, uint32_t ic, const unsigned char *k) - __attribute__ ((nonnull)); + __attribute__ ((nonnull(1, 4, 6))); void crypto_stream_chacha20_ietf_keygen(unsigned char k[crypto_stream_chacha20_ietf_KEYBYTES]) __attribute__ ((nonnull)); @@ -431,7 +431,7 @@ int crypto_aead_xchacha20poly1305_ietf_decrypt_detached(unsigned char *m, unsigned long long adlen, const unsigned char *npub, const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5, 9, 9))); + __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5, 8, 9))); void crypto_aead_xchacha20poly1305_ietf_keygen(unsigned char k[crypto_aead_xchacha20poly1305_ietf_KEYBYTES]) __attribute__ ((nonnull)); @@ -514,13 +514,13 @@ int crypto_onetimeauth_poly1305(unsigned char *out, const unsigned char *in, unsigned long long inlen, const unsigned char *k) - __attribute__ ((nonnull)); + __attribute__ ((nonnull(1, 4))); int crypto_onetimeauth_poly1305_verify(const unsigned char *h, const unsigned char *in, unsigned long long inlen, const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); + __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); int crypto_onetimeauth_poly1305_init(crypto_onetimeauth_poly1305_state *state, const unsigned char *key) @@ -529,7 +529,7 @@ int crypto_onetimeauth_poly1305_init(crypto_onetimeauth_poly1305_state *state, int crypto_onetimeauth_poly1305_update(crypto_onetimeauth_poly1305_state *state, const unsigned char *in, unsigned long long inlen) - __attribute__ ((nonnull)); + __attribute__ ((nonnull(1))); int crypto_onetimeauth_poly1305_final(crypto_onetimeauth_poly1305_state *state, unsigned char *out) @@ -572,11 +572,11 @@ const char *crypto_onetimeauth_primitive(void); int crypto_onetimeauth(unsigned char *out, const unsigned char *in, unsigned long long inlen, const unsigned char *k) - __attribute__ ((nonnull)); + __attribute__ ((nonnull(1, 4))); int crypto_onetimeauth_verify(const unsigned char *h, const unsigned char *in, unsigned long long inlen, const unsigned char *k) - __attribute__ ((warn_unused_result)) __attribute__ ((nonnull)); + __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4))); int crypto_onetimeauth_init(crypto_onetimeauth_state *state, const unsigned char *key) __attribute__ ((nonnull)); @@ -584,7 +584,7 @@ int crypto_onetimeauth_init(crypto_onetimeauth_state *state, int crypto_onetimeauth_update(crypto_onetimeauth_state *state, const unsigned char *in, unsigned long long inlen) - __attribute__ ((nonnull)); + __attribute__ ((nonnull(1))); int crypto_onetimeauth_final(crypto_onetimeauth_state *state, unsigned char *out) __attribute__ ((nonnull));