From 5886869bab2fdda9ab40ffdfe0ce8f9a76e4092e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 10 Aug 2026 09:47:38 +0930 Subject: [PATCH 1/2] CCAN: update to post-kimi audit. Lots of changes, so I've summarized the individual upstream commits. May alter (fix!) CLN behavior: * 6b4e62ec tal/path: path_simplify() ".." handling * 4ceeef2b io: don't busy-spin with exclusive conn + pending always plan * 64822afc io: listener fds O_NONBLOCK * b02c1147, 2f04573d, 42486358 pipecmd: _exit, pre-fork fd leak, EINTR * f7062e12, 6da9c78d fdpass: reject-path fd leak, errno sign * 339b359a breakpoint: blocked-SIGTRAP + fork staleness (--dev-debugger only) * 018a4977 take: keep labelarr in sync in taken() (debug builds would give wrong taken() error) * 28f7c3fe check_type: hard-fail (would cause build-time failure) The rest are not patterns used by CLN, or only happen on allocation failure (which always causes immediate exit): * 4122d490 tal/str: count regex capture groups via re_nsub, not text scan. No such complex regexes used. * 5b6fca2c tal: allow tal_steal() to rescue an object being destroyed; we don't do this. * tal: 9da96c88 (CCAN_LIST_DEBUG only), ca38d33a (notifier edge), 328d96ff (needs type-mismatched tal_expand), 39d1a0a0 (harmless thinko), df66b082 (path_readlink unused), 80416994 (UB never dereferenced), 0e926004 (doc), 3217faca (zero st_size; no /proc reads in CLN), 5d86e1fe (doc) * c0c9797f str: strcount empty needle - both CLN call sites pass constant non-empty needles * htable: af8ff95e (init_sized unused), d3da4521 (32-bit), ba8c4ad2 (needs billions of entries) * opt: 6c3faecc (bare '-' argv), dae5f5f4 (attribute/doc; CLN passes real args) * timer: f7b21467 (36k years at default granularity), 1127eb30 (needs failing custom allocator - CLN aborts on OOM) * mem: 351072ac (memcchr unused), dde9474c (fallback memrchr - glibc/musl provide it), 228161d9 (test), f5b48d0f (doc) * json_out: f14769e3, e9893f9e, d0401012, c91ae47b - all OOM/encoding-error paths; CLN aborts on OOM * membuf: 4ee105f0 (UB, correct results anyway), 4b2d55b8 (2^62-element buffers) * rune: cc63dceb (rune_dup unused), 51257cd9 (CLN fieldnames are alnum), fb4dbfa6 (needs embedded-NUL field values) * strset/strmap iterator rework: b5dcf30e, 19b73d88, 51c4da92, c127ff33 - additive API + recursion fix; CLN's maps are far too small to hit deep trees * crypto: 1c377ffb (32-bit only), 344ba97c (needs 8160-byte HKDF requests), b4316b44 (default SHACHAIN_BITS=64 immune) * f2b445b3 str/base32: CLN's one call site passes an exact-size buffer * intmap 1a1bb440 (sintmap unused), bitmap ab052a14 (2^61 bits), likely 8cfa5f8c (likely_stats unused), take e30e011d (only take_cleanup() on exit), rbuf 748a0692 (rbuf_fill unused), ptr_valid x4 (unused), cdump x3 (cdump_extract unused) * asort: 0d54244f, a13c9b97, d878b036 - fallback/build-hygiene only; CLN on glibc takes the qsort_r path and already compiled * Doc-only: 2b988863, 3e034139, 8fee253f, e3155f93, 6e830d68, 172ae94c, cbbd603e, 6f0b1b03 * Test-only: 46acc9b6, 9df9809e, 4c23c54c * 5b2773b2 build_assert: _Static_assert - compile-time only; all 138 CLN uses are constants (same 138-use build check applies as check_type) * 01fbf784 container_of: NULL+0 UB fix - behavior identical * 0421034f cppmagic sentinel, 43cfe853 ilog ilog64 - portability/performance only Signed-off-by: Rusty Russell --- ccan/README | 2 +- .../array_size/test/compile_fail-void-ptr.c | 19 ++ ccan/ccan/asort/asort.c | 16 +- ccan/ccan/asort/asort.h | 6 + ccan/ccan/asort/test/run-fallback-build.c | 94 ++++++++ ccan/ccan/asort/test/run-include-order.c | 45 ++++ ccan/ccan/bitmap/bitmap.h | 2 +- ccan/ccan/bitmap/test/run-sizeof-overflow.c | 48 ++++ ccan/ccan/bitops/bitops.h | 2 +- ccan/ccan/breakpoint/breakpoint.c | 23 +- ccan/ccan/breakpoint/breakpoint.h | 9 +- ccan/ccan/breakpoint/test/run-blocked.c | 41 ++++ ccan/ccan/breakpoint/test/run-fork.c | 44 ++++ ccan/ccan/breakpoint/test/run-threads.c | 66 ++++++ ccan/ccan/build_assert/build_assert.h | 30 ++- .../test/compile_fail-expr-nonconst.c | 20 ++ .../build_assert/test/compile_fail-nonconst.c | 26 +++ ccan/ccan/cdump/cdump.c | 35 ++- ccan/ccan/cdump/test/run-eof-builtin-type.c | 39 ++++ ccan/ccan/cdump/test/run-expr-recursion.c | 44 ++++ ccan/ccan/cdump/test/run-trailing-ident.c | 45 ++++ .../cdump/test/run-unterminated-comment.c | 30 +++ ccan/ccan/check_type/check_type.h | 10 +- .../test/compile_fail-check_type_void.c | 21 ++ .../compile_fail-check_types_match_void.c | 17 ++ ccan/ccan/compiler/compiler.h | 22 +- ccan/ccan/compiler/test/run-predefine-const.c | 20 ++ ccan/ccan/container_of/container_of.h | 7 +- ccan/ccan/container_of/test/run-or-null.c | 35 +++ ccan/ccan/cppmagic/cppmagic.h | 13 +- ccan/ccan/cppmagic/test/run-map-empty-arg.c | 47 ++++ ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.c | 5 +- ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.h | 2 +- .../crypto/hkdf_sha256/test/run-max-okm.c | 57 +++++ ccan/ccan/crypto/sha256/sha256.h | 3 +- .../ccan/crypto/sha256/test/run-length-wrap.c | 52 +++++ ccan/ccan/crypto/shachain/shachain.c | 9 + .../crypto/shachain/test/run-exhaust-wrap.c | 83 +++++++ ccan/ccan/err/test/run.c | 4 - ccan/ccan/fdpass/fdpass.c | 20 +- ccan/ccan/fdpass/test/run-errno.c | 44 ++++ ccan/ccan/fdpass/test/run-multifd-leak.c | 100 +++++++++ ccan/ccan/htable/htable.c | 43 ++-- .../htable/test/run-init-sized-alloc-fail.c | 79 +++++++ ccan/ccan/htable/test/run-init-sized-huge.c | 79 +++++++ ccan/ccan/ilog/ilog.h | 2 +- ccan/ccan/intmap/intmap.h | 6 +- .../intmap/test/run-signed-firstlast-empty.c | 56 +++++ ccan/ccan/io/io.c | 5 + ccan/ccan/io/poll.c | 16 +- .../io/test/run-49-exclusive-always-spin.c | 86 ++++++++ .../io/test/run-50-listener-blocking-accept.c | 127 +++++++++++ ccan/ccan/json_escape/json_escape.c | 2 + ccan/ccan/json_out/json_out.c | 28 ++- ccan/ccan/json_out/test/run-oom-escape.c | 72 ++++++ ccan/ccan/json_out/test/run-oom.c | 108 +++++++++ ccan/ccan/json_out/test/run-vsnprintf-error.c | 53 +++++ ccan/ccan/likely/likely.c | 2 +- ccan/ccan/likely/test/run-stats-empty-debug.c | 44 ++++ ccan/ccan/mem/mem.c | 6 +- ccan/ccan/mem/mem.h | 3 + ccan/ccan/mem/test/api.c | 8 +- ccan/ccan/mem/test/run-memcchr-range.c | 45 ++++ ccan/ccan/mem/test/run-memrchr-fallback.c | 46 ++++ ccan/ccan/membuf/membuf.c | 10 + ccan/ccan/membuf/membuf.h | 4 + ccan/ccan/membuf/test/run-null-init.c | 48 ++++ ccan/ccan/membuf/test/run-overflow.c | 68 ++++++ ccan/ccan/opt/helpers.c | 3 +- ccan/ccan/opt/opt.h | 10 +- ccan/ccan/opt/parse.c | 5 +- .../ccan/opt/test/run-early-incomplete-dash.c | 57 +++++ ccan/ccan/order/test/run-fancy.c | 8 +- ccan/ccan/pipecmd/pipecmd.c | 24 +- ccan/ccan/pipecmd/test/run-eintr.c | 56 +++++ ccan/ccan/pipecmd/test/run-execfail-fdleak.c | 88 ++++++++ ccan/ccan/pipecmd/test/run-execfail-flush.c | 75 +++++++ ccan/ccan/ptr_valid/ptr_valid.c | 36 ++- ccan/ccan/ptr_valid/ptr_valid.h | 1 + ccan/ccan/ptr_valid/test/run-batch-cache.c | 40 ++++ ccan/ccan/ptr_valid/test/run-child-flush.c | 74 +++++++ ccan/ccan/ptr_valid/test/run-errno.c | 43 ++++ ccan/ccan/ptr_valid/test/run-realloc-leak.c | 122 +++++++++++ ccan/ccan/rbuf/rbuf.c | 8 +- ccan/ccan/rbuf/test/run-fill-eof.c | 76 +++++++ ccan/ccan/rune/coding.c | 3 +- ccan/ccan/rune/rune.c | 19 +- ccan/ccan/rune/test/run-dup-unique-id.c | 63 ++++++ ccan/ccan/rune/test/run-fieldname-punct.c | 66 ++++++ ccan/ccan/rune/test/run-lexo-embedded-nul.c | 49 +++++ ccan/ccan/short_types/_info | 2 +- ccan/ccan/str/base32/base32.c | 2 +- .../base32/test/run-encode-oversized-dest.c | 39 ++++ ccan/ccan/str/hex/_info | 2 +- ccan/ccan/str/str.c | 3 + .../ccan/str/test/run-strcount-empty-needle.c | 20 ++ ccan/ccan/strmap/_info | 1 + ccan/ccan/strmap/strmap.c | 194 ++++++++++++++-- ccan/ccan/strmap/strmap.h | 70 ++++++ ccan/ccan/strmap/test/run-getn-embedded-nul.c | 67 ++++++ ccan/ccan/strmap/test/run-iter.c | 134 ++++++++++++ ccan/ccan/strset/strset.c | 207 ++++++++++++++++-- ccan/ccan/strset/strset.h | 56 +++++ ccan/ccan/strset/test/run-deep-recursion.c | 99 +++++++++ ccan/ccan/strset/test/run-iter.c | 124 +++++++++++ ccan/ccan/structeq/structeq.h | 4 + ccan/ccan/take/take.c | 5 + ccan/ccan/take/test/run-allocfail-cleanup.c | 46 ++++ ccan/ccan/take/test/run-debug-labels.c | 52 +++++ ccan/ccan/tal/grab_file/grab_file.c | 4 +- ccan/ccan/tal/grab_file/test/run-size0.c | 25 +++ ccan/ccan/tal/link/link.h | 3 + ccan/ccan/tal/path/path.c | 30 ++- ccan/ccan/tal/path/path.h | 3 + ccan/ccan/tal/path/test/run-simplify.c | 29 ++- ccan/ccan/tal/str/str.c | 83 +++---- ccan/ccan/tal/str/test/run-strreg-bracket.c | 29 +++ ccan/ccan/tal/tal.c | 76 ++++++- ccan/ccan/tal/tal.h | 28 ++- ccan/ccan/tal/test/run-expand-odd.c | 31 +++ ccan/ccan/tal/test/run-notifier-delchild.c | 57 +++++ ccan/ccan/tal/test/run-steal-destroy.c | 54 +++++ .../tal/test/run-steal-rescue-allocfail.c | 80 +++++++ ccan/ccan/tcon/tcon.h | 4 + ccan/ccan/time/test/run-divide-roundup.c | 43 ++++ ccan/ccan/time/test/run-multiply-overflow.c | 35 +++ ccan/ccan/time/time.c | 28 ++- ccan/ccan/time/time.h | 3 + ccan/ccan/timer/test/run-expire-alloc-fail.c | 64 ++++++ ccan/ccan/timer/test/run-far-level12.c | 54 +++++ ccan/ccan/timer/timer.c | 27 ++- .../test/compile_fail-typesafe_cb-int.c | 5 +- .../compile_ok-typesafe_cb-unprototyped.c | 31 +++ ccan/ccan/typesafe_cb/typesafe_cb.h | 5 + ccan/tools/configurator/configurator.c | 3 + 135 files changed, 4969 insertions(+), 221 deletions(-) create mode 100644 ccan/ccan/array_size/test/compile_fail-void-ptr.c create mode 100644 ccan/ccan/asort/test/run-fallback-build.c create mode 100644 ccan/ccan/asort/test/run-include-order.c create mode 100644 ccan/ccan/bitmap/test/run-sizeof-overflow.c create mode 100644 ccan/ccan/breakpoint/test/run-blocked.c create mode 100644 ccan/ccan/breakpoint/test/run-fork.c create mode 100644 ccan/ccan/breakpoint/test/run-threads.c create mode 100644 ccan/ccan/build_assert/test/compile_fail-expr-nonconst.c create mode 100644 ccan/ccan/build_assert/test/compile_fail-nonconst.c create mode 100644 ccan/ccan/cdump/test/run-eof-builtin-type.c create mode 100644 ccan/ccan/cdump/test/run-expr-recursion.c create mode 100644 ccan/ccan/cdump/test/run-trailing-ident.c create mode 100644 ccan/ccan/cdump/test/run-unterminated-comment.c create mode 100644 ccan/ccan/check_type/test/compile_fail-check_type_void.c create mode 100644 ccan/ccan/check_type/test/compile_fail-check_types_match_void.c create mode 100644 ccan/ccan/compiler/test/run-predefine-const.c create mode 100644 ccan/ccan/container_of/test/run-or-null.c create mode 100644 ccan/ccan/cppmagic/test/run-map-empty-arg.c create mode 100644 ccan/ccan/crypto/hkdf_sha256/test/run-max-okm.c create mode 100644 ccan/ccan/crypto/sha256/test/run-length-wrap.c create mode 100644 ccan/ccan/crypto/shachain/test/run-exhaust-wrap.c create mode 100644 ccan/ccan/fdpass/test/run-errno.c create mode 100644 ccan/ccan/fdpass/test/run-multifd-leak.c create mode 100644 ccan/ccan/htable/test/run-init-sized-alloc-fail.c create mode 100644 ccan/ccan/htable/test/run-init-sized-huge.c create mode 100644 ccan/ccan/intmap/test/run-signed-firstlast-empty.c create mode 100644 ccan/ccan/io/test/run-49-exclusive-always-spin.c create mode 100644 ccan/ccan/io/test/run-50-listener-blocking-accept.c create mode 100644 ccan/ccan/json_out/test/run-oom-escape.c create mode 100644 ccan/ccan/json_out/test/run-oom.c create mode 100644 ccan/ccan/json_out/test/run-vsnprintf-error.c create mode 100644 ccan/ccan/likely/test/run-stats-empty-debug.c create mode 100644 ccan/ccan/mem/test/run-memcchr-range.c create mode 100644 ccan/ccan/mem/test/run-memrchr-fallback.c create mode 100644 ccan/ccan/membuf/test/run-null-init.c create mode 100644 ccan/ccan/membuf/test/run-overflow.c create mode 100644 ccan/ccan/opt/test/run-early-incomplete-dash.c create mode 100644 ccan/ccan/pipecmd/test/run-eintr.c create mode 100644 ccan/ccan/pipecmd/test/run-execfail-fdleak.c create mode 100644 ccan/ccan/pipecmd/test/run-execfail-flush.c create mode 100644 ccan/ccan/ptr_valid/test/run-batch-cache.c create mode 100644 ccan/ccan/ptr_valid/test/run-child-flush.c create mode 100644 ccan/ccan/ptr_valid/test/run-errno.c create mode 100644 ccan/ccan/ptr_valid/test/run-realloc-leak.c create mode 100644 ccan/ccan/rbuf/test/run-fill-eof.c create mode 100644 ccan/ccan/rune/test/run-dup-unique-id.c create mode 100644 ccan/ccan/rune/test/run-fieldname-punct.c create mode 100644 ccan/ccan/rune/test/run-lexo-embedded-nul.c create mode 100644 ccan/ccan/str/base32/test/run-encode-oversized-dest.c create mode 100644 ccan/ccan/str/test/run-strcount-empty-needle.c create mode 100644 ccan/ccan/strmap/test/run-getn-embedded-nul.c create mode 100644 ccan/ccan/strmap/test/run-iter.c create mode 100644 ccan/ccan/strset/test/run-deep-recursion.c create mode 100644 ccan/ccan/strset/test/run-iter.c create mode 100644 ccan/ccan/take/test/run-allocfail-cleanup.c create mode 100644 ccan/ccan/take/test/run-debug-labels.c create mode 100644 ccan/ccan/tal/grab_file/test/run-size0.c create mode 100644 ccan/ccan/tal/str/test/run-strreg-bracket.c create mode 100644 ccan/ccan/tal/test/run-expand-odd.c create mode 100644 ccan/ccan/tal/test/run-notifier-delchild.c create mode 100644 ccan/ccan/tal/test/run-steal-destroy.c create mode 100644 ccan/ccan/tal/test/run-steal-rescue-allocfail.c create mode 100644 ccan/ccan/time/test/run-divide-roundup.c create mode 100644 ccan/ccan/time/test/run-multiply-overflow.c create mode 100644 ccan/ccan/timer/test/run-expire-alloc-fail.c create mode 100644 ccan/ccan/timer/test/run-far-level12.c create mode 100644 ccan/ccan/typesafe_cb/test/compile_ok-typesafe_cb-unprototyped.c diff --git a/ccan/README b/ccan/README index 80a88ad447a0..a5272fb35445 100644 --- a/ccan/README +++ b/ccan/README @@ -1,3 +1,3 @@ CCAN imported from https://github.com/rustyrussell/ccan. -CCAN version: fe99a8e0 +CCAN version: 08af1cab diff --git a/ccan/ccan/array_size/test/compile_fail-void-ptr.c b/ccan/ccan/array_size/test/compile_fail-void-ptr.c new file mode 100644 index 000000000000..510279289d0f --- /dev/null +++ b/ccan/ccan/array_size/test/compile_fail-void-ptr.c @@ -0,0 +1,19 @@ +#include + +int main(void) +{ + void *vp = (void *)0; +#ifdef FAIL + /* A void * is a pointer, not an array: the typeof/types_compatible_p + * guard must reject this (unlike comparison-based checks, which a + * void type silently bypasses -- see the check_type/container_of + * audits). */ + return ARRAY_SIZE(vp); +#if !HAVE_TYPEOF || !HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if _array_size_chk is a noop." +#endif +#else + (void)vp; + return 0; +#endif +} diff --git a/ccan/ccan/asort/asort.c b/ccan/ccan/asort/asort.c index b90891ea199e..4607f907088f 100644 --- a/ccan/ccan/asort/asort.c +++ b/ccan/ccan/asort/asort.c @@ -34,11 +34,15 @@ #include #include +/* Vendored glibc code uses GNU void * arithmetic. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-arith" + /* glibc-internal type, mapped to ccan's equivalent. */ typedef _total_order_cb __compar_d_fn_t; /* glibc-internal helpers, not available outside glibc. */ -static inline void *__mempcpy(void *dst, const void *src, size_t n) +static inline void *asort_mempcpy(void *dst, const void *src, size_t n) { return (char *) memcpy (dst, src, n) + n; } @@ -54,8 +58,8 @@ __memswap (void *__restrict p1, void *__restrict p2, size_t n) while (n > SWAP_GENERIC_SIZE) { memcpy (tmp, p1, SWAP_GENERIC_SIZE); - p1 = __mempcpy (p1, p2, SWAP_GENERIC_SIZE); - p2 = __mempcpy (p2, tmp, SWAP_GENERIC_SIZE); + p1 = asort_mempcpy (p1, p2, SWAP_GENERIC_SIZE); + p2 = asort_mempcpy (p2, tmp, SWAP_GENERIC_SIZE); n -= SWAP_GENERIC_SIZE; } while (n > 0) @@ -316,13 +320,13 @@ msort_with_tmp (const struct msort_param *p, void *b, size_t n) { if (cmp (b1, b2, arg) <= 0) { - tmp = (char *) __mempcpy (tmp, b1, s); + tmp = (char *) asort_mempcpy (tmp, b1, s); b1 += s; --n1; } else { - tmp = (char *) __mempcpy (tmp, b2, s); + tmp = (char *) asort_mempcpy (tmp, b2, s); b2 += s; --n2; } @@ -452,4 +456,6 @@ _asort (void *const pbase, size_t total_elems, size_t size, } } +#pragma GCC diagnostic pop + #endif /* !HAVE_QSORT_R_PRIVATE_LAST */ diff --git a/ccan/ccan/asort/asort.h b/ccan/ccan/asort/asort.h index 43b0f89c3c6b..4f5ab24028da 100644 --- a/ccan/ccan/asort/asort.h +++ b/ccan/ccan/asort/asort.h @@ -23,6 +23,12 @@ _asort((base), (num), sizeof(*(base)), \ total_order_cast((cmp), *(base), (ctx)), (ctx)) #if HAVE_QSORT_R_PRIVATE_LAST +/* qsort_r is only declared under _GNU_SOURCE, which must precede the + * first libc include — we can't control our includers, so declare it + * ourselves (the configurator only sets this where this GNU signature + * was detected). */ +void qsort_r(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); #define _asort(b, n, s, cmp, ctx) qsort_r(b, n, s, cmp, ctx) #else void _asort(void *base, size_t nmemb, size_t size, diff --git a/ccan/ccan/asort/test/run-fallback-build.c b/ccan/ccan/asort/test/run-fallback-build.c new file mode 100644 index 000000000000..107f3fdd6701 --- /dev/null +++ b/ccan/ccan/asort/test/run-fallback-build.c @@ -0,0 +1,94 @@ +/* Regression test for the !HAVE_QSORT_R_PRIVATE_LAST fallback in asort.c. + * + * The vendored glibc mergesort fallback must compile on any supported + * platform, including glibc systems where config.h legitimately has + * HAVE_QSORT_R_PRIVATE_LAST == 0 (e.g. ccanlint's reduce_features + * check, hand-written or cross-compilation configs). glibc's string.h + * declares __mempcpy under _DEFAULT_SOURCE (on by default), so the + * fallback's "static inline void *__mempcpy(...)" helper collides with + * the libc declaration: "error: static declaration of '__mempcpy' + * follows non-static declaration". This test forces the fallback on + * and therefore fails to compile on glibc today; after the helper is + * renamed it must compile and pass everywhere. + */ +#include "config.h" +#undef HAVE_QSORT_R_PRIVATE_LAST +#define HAVE_QSORT_R_PRIVATE_LAST 0 +#include +#include +#include +#include +#include +#include + +static int cmp_int(const int *a, const int *b, int *count) +{ + (*count)++; + return (*a > *b) - (*a < *b); +} + +struct big { char pad[9]; int key; char pad2[31]; }; /* 44 bytes: indirect */ +static int cmp_big(const struct big *a, const struct big *b, int *count) +{ + (*count)++; + return (a->key > b->key) - (a->key < b->key); +} + +static bool sorted_ints(const int *a, size_t n) +{ + for (size_t i = 1; i < n; i++) + if (a[i-1] > a[i]) + return false; + return true; +} + +int main(void) +{ + alarm(10); + plan_tests(3); + + /* Small array: mergesort with the stack buffer. */ + { + int a[50]; + int count = 0; + for (size_t i = 0; i < 50; i++) + a[i] = (int)((i * 37 + 11) % 23); + asort(a, 50, cmp_int, &count); + ok1(sorted_ints(a, 50) && count > 0); + } + + /* 40000 bytes: exceeds QSORT_STACK_SIZE, takes the malloc path. */ + { + size_t n = 10000; + int *a = malloc(n * sizeof(*a)); + int count = 0; + unsigned long long r = 12345; + for (size_t i = 0; i < n; i++) { + r ^= r << 13; r ^= r >> 7; r ^= r << 17; + a[i] = (int)r; + } + asort(a, n, cmp_int, &count); + ok1(sorted_ints(a, n)); + free(a); + } + + /* Elements larger than 32 bytes: indirect (pointer) mergesort. */ + { + size_t n = 100; + struct big *b = malloc(n * sizeof(*b)); + int count = 0; + bool ok = true; + for (size_t i = 0; i < n; i++) { + memset(&b[i], (int)(i & 0xff), sizeof(*b)); + b[i].key = (int)((i * 53) % 31); + } + asort(b, n, cmp_big, &count); + for (size_t i = 1; i < n; i++) + if (b[i-1].key > b[i].key) + ok = false; + ok1(ok); + free(b); + } + + return exit_status(); +} diff --git a/ccan/ccan/asort/test/run-include-order.c b/ccan/ccan/asort/test/run-include-order.c new file mode 100644 index 000000000000..a6a0929b8288 --- /dev/null +++ b/ccan/ccan/asort/test/run-include-order.c @@ -0,0 +1,45 @@ +/* Regression test: asort.h must be usable from a translation unit that + * included libc headers before it. + * + * With HAVE_QSORT_R_PRIVATE_LAST == 1 the asort() macro expands to a + * direct call of qsort_r (asort.h:26), but glibc only declares qsort_r + * under _GNU_SOURCE, and config.h's "#define _GNU_SOURCE" comes too + * late once any libc header (via features.h) was already processed. + * The result is a call to an undeclared function: a hard error on + * clang >= 15 and gcc >= 14 (C99+ implicit function declarations), a + * warning plus UB-ish implicit decl on older gcc. Today this file + * fails to compile under clang; after routing qsort_r through a real + * _asort() function defined in asort.c (where config.h is included + * first) it must compile cleanly and pass. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +static int cmp_int(const int *a, const int *b, void *ctx) +{ + (void)ctx; + return (*a > *b) - (*a < *b); +} + +int main(void) +{ + int a[7] = { 7, 1, 6, 2, 5, 3, 4 }; + bool ok = true; + + alarm(10); + plan_tests(1); + + asort(a, 7, cmp_int, NULL); + for (size_t i = 1; i < 7; i++) + if (a[i-1] > a[i]) + ok = false; + ok1(ok); + + return exit_status(); +} diff --git a/ccan/ccan/bitmap/bitmap.h b/ccan/ccan/bitmap/bitmap.h index e1bf4bb761d2..466fc867c848 100644 --- a/ccan/ccan/bitmap/bitmap.h +++ b/ccan/ccan/bitmap/bitmap.h @@ -13,7 +13,7 @@ typedef unsigned long bitmap_word; #define BITMAP_WORD_BITS (sizeof(bitmap_word) * CHAR_BIT) #define BITMAP_NWORDS(_n) \ - (((_n) + BITMAP_WORD_BITS - 1) / BITMAP_WORD_BITS) + (((_n) / BITMAP_WORD_BITS) + (((_n) % BITMAP_WORD_BITS) != 0)) #define BITMAP_WORD_0 (0) #define BITMAP_WORD_1 ((bitmap_word)-1UL) diff --git a/ccan/ccan/bitmap/test/run-sizeof-overflow.c b/ccan/ccan/bitmap/test/run-sizeof-overflow.c new file mode 100644 index 000000000000..ff98ba2eadcd --- /dev/null +++ b/ccan/ccan/bitmap/test/run-sizeof-overflow.c @@ -0,0 +1,48 @@ +/* Regression test for the allocation-sizing overflow in BITMAP_NWORDS / + * bitmap_sizeof (ccan/bitmap/bitmap.h:15-16, 31-34). + * + * BITMAP_NWORDS(_n) computes ((_n) + BITMAP_WORD_BITS - 1) / + * BITMAP_WORD_BITS; the addition wraps for nbits within + * BITMAP_WORD_BITS-1 of ULONG_MAX, so bitmap_sizeof() returns a tiny + * size (0 for ULONG_MAX) and bitmap_alloc() then returns a non-NULL + * pointer to a 0-byte allocation that purports to hold ~2^64 bits; the + * first bitmap_set_bit() writes out of bounds (observed: ASan + * heap-buffer-overflow, access of size 8 on a 1-byte region). + * + * Correct behavior: the reference computation below (divide first, + * then round up) never overflows and its product with + * sizeof(bitmap_word) still fits in size_t on both LP64 and ILP32, so + * bitmap_sizeof() must equal it for every nbits, including ULONG_MAX + * (bitmap_alloc() will then correctly fail with NULL for the + * unmappable ~2^61-byte request). + */ +#include +#include +#include +#include +#include + +static unsigned long ref_nwords(unsigned long nbits) +{ + return nbits / BITMAP_WORD_BITS + ((nbits % BITMAP_WORD_BITS) != 0); +} + +int main(void) +{ + /* Largest nbits that does NOT wrap the + (BITS-1) addition. */ + unsigned long edge = ULONG_MAX - (BITMAP_WORD_BITS - 1); + + alarm(10); + plan_tests(4); + + /* These two fail against the current code (both return 0). */ + ok1(BITMAP_NWORDS(ULONG_MAX) == ref_nwords(ULONG_MAX)); + ok1(bitmap_sizeof(ULONG_MAX) == + ref_nwords(ULONG_MAX) * sizeof(bitmap_word)); + + /* Boundary sanity: these already pass today and must keep passing. */ + ok1(BITMAP_NWORDS(edge) == ref_nwords(edge)); + ok1(bitmap_sizeof(edge) == ref_nwords(edge) * sizeof(bitmap_word)); + + return exit_status(); +} diff --git a/ccan/ccan/bitops/bitops.h b/ccan/ccan/bitops/bitops.h index 4e81f5716fb3..b2a3bc7f0e07 100644 --- a/ccan/ccan/bitops/bitops.h +++ b/ccan/ccan/bitops/bitops.h @@ -26,7 +26,7 @@ static inline int bitops_ffs32(uint32_t u) /** * bitops_ffs64: find lowest set bit in a uint64_t * - * Returns 1 for least significant bit, 32 for most significant bit, 0 + * Returns 1 for least significant bit, 64 for most significant bit, 0 * for no bits set. */ static inline int bitops_ffs64(uint64_t u) diff --git a/ccan/ccan/breakpoint/breakpoint.c b/ccan/ccan/breakpoint/breakpoint.c index 279e29a1dfd1..30532ec69a88 100644 --- a/ccan/ccan/breakpoint/breakpoint.c +++ b/ccan/ccan/breakpoint/breakpoint.c @@ -7,26 +7,39 @@ bool breakpoint_initialized; bool breakpoint_under_debug; +pid_t breakpoint_pid; + +static volatile sig_atomic_t trapped; /* This doesn't get called if we're under GDB. */ static void trap(int signum) { - breakpoint_initialized = true; + trapped = true; } void breakpoint_init(void) { struct sigaction old, new; + sigset_t mask, oldmask; new.sa_handler = trap; new.sa_flags = 0; sigemptyset(&new.sa_mask); sigaction(SIGTRAP, &new, &old); + + /* If SIGTRAP is blocked, the probe would pend (and kill us when + * the caller restores its mask), not run the handler. */ + sigemptyset(&mask); + sigaddset(&mask, SIGTRAP); + sigprocmask(SIG_UNBLOCK, &mask, &oldmask); + + trapped = false; kill(getpid(), SIGTRAP); + + sigprocmask(SIG_SETMASK, &oldmask, NULL); sigaction(SIGTRAP, &old, NULL); - if (!breakpoint_initialized) { - breakpoint_initialized = true; - breakpoint_under_debug = true; - } + breakpoint_pid = getpid(); + breakpoint_initialized = true; + breakpoint_under_debug = !trapped; } diff --git a/ccan/ccan/breakpoint/breakpoint.h b/ccan/ccan/breakpoint/breakpoint.h index 6283a01052a9..1a0c36d24845 100644 --- a/ccan/ccan/breakpoint/breakpoint.h +++ b/ccan/ccan/breakpoint/breakpoint.h @@ -10,13 +10,20 @@ void breakpoint_init(void) COLD; extern bool breakpoint_initialized; extern bool breakpoint_under_debug; +extern pid_t breakpoint_pid; /** * breakpoint - stop if running under the debugger. + * + * The first call detects the debugger via a SIGTRAP probe. This is + * not thread-safe: either call breakpoint_init() explicitly at + * program start (before creating threads), or don't let first use + * race. */ static inline void breakpoint(void) { - if (!breakpoint_initialized) + /* Detection state doesn't carry across fork(). */ + if (!breakpoint_initialized || breakpoint_pid != getpid()) breakpoint_init(); if (breakpoint_under_debug) kill(getpid(), SIGTRAP); diff --git a/ccan/ccan/breakpoint/test/run-blocked.c b/ccan/ccan/breakpoint/test/run-blocked.c new file mode 100644 index 000000000000..1d52fb69a126 --- /dev/null +++ b/ccan/ccan/breakpoint/test/run-blocked.c @@ -0,0 +1,41 @@ +/* Regression test for: breakpoint_init() does not handle SIGTRAP being + * blocked in the calling thread. + * + * With SIGTRAP blocked, the kill(getpid(), SIGTRAP) in breakpoint_init + * only *pends* the signal; the trap handler never runs, so the module + * concludes "under debugger" (wrong: no debugger is attached), and the + * pending SIGTRAP is delivered later - when the caller restores its + * signal mask - to the caller's original disposition (default: + * terminate + core dump). + * + * Fails against the current code: the second ok1 fails + * (breakpoint_under_debug == true) and the process is then killed by + * SIGTRAP when the mask is restored. Must pass after repair. */ +#include +#include +#include +#include +#include + +int main(void) +{ + sigset_t set, oset; + + alarm(10); + plan_tests(3); + + sigemptyset(&set); + sigaddset(&set, SIGTRAP); + ok1(sigprocmask(SIG_BLOCK, &set, &oset) == 0); + + /* Not under a debugger; blocking SIGTRAP must not change that. */ + breakpoint(); + ok1(breakpoint_initialized && !breakpoint_under_debug); + + /* Restoring the caller's signal mask must be safe: the module + * must not leave a SIGTRAP pending behind the caller's back. */ + sigprocmask(SIG_SETMASK, &oset, NULL); + ok1(true); + + return exit_status(); +} diff --git a/ccan/ccan/breakpoint/test/run-fork.c b/ccan/ccan/breakpoint/test/run-fork.c new file mode 100644 index 000000000000..66a383508205 --- /dev/null +++ b/ccan/ccan/breakpoint/test/run-fork.c @@ -0,0 +1,44 @@ +/* Regression test for: debugger-detection state is stale across fork(). + * + * breakpoint_initialized/breakpoint_under_debug are plain globals + * inherited by the child. When the parent detected a debugger and then + * forks, the child is NOT being traced (gdb's default + * follow-fork-mode=parent detaches the child), but inherits + * breakpoint_under_debug=true, so its breakpoint() sends itself SIGTRAP + * with the default disposition: terminated + core dump, although it is + * not running under a debugger. Verified naturally under gdb 15.1 + * (see audit-findings/breakpoint.md F3); this test reproduces the exact + * post-fork state directly. + * + * Fails against the current code (child killed by SIGTRAP). Must pass + * after repair. */ +#include +#include +#include +#include +#include + +int main(void) +{ + pid_t pid; + int status; + + alarm(10); + plan_tests(1); + + /* State the child inherits after the parent ran breakpoint() + * under a debugger. */ + breakpoint_initialized = true; + breakpoint_under_debug = true; + + pid = fork(); + if (pid == 0) { + /* Child is not traced: breakpoint() must do nothing. */ + breakpoint(); + exit(0); + } + waitpid(pid, &status, 0); + ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0); + + return exit_status(); +} diff --git a/ccan/ccan/breakpoint/test/run-threads.c b/ccan/ccan/breakpoint/test/run-threads.c new file mode 100644 index 000000000000..a765e9b8f437 --- /dev/null +++ b/ccan/ccan/breakpoint/test/run-threads.c @@ -0,0 +1,66 @@ +/* Regression test for: breakpoint_init() is not thread-safe. + * + * Fatal interleaving of two concurrent breakpoint_init() calls (both + * reachable via the public breakpoint() on first use): + * A: sigaction(trap, &oldA=orig) + * B: sigaction(trap, &oldB=trap) (B saves A's handler as "old") + * A: kill -> trap runs + * A: sigaction(oldA) (disposition back to orig) + * B: kill -> SIGTRAP with original (default) disposition + * -> process terminated + core dump. + * + * Each child round resets breakpoint_initialized to re-enter the + * uninitialized state, simulating many first-use races. Fails against + * the current code (a child is killed by SIGTRAP, usually in round 0). + * Must pass after repair. */ +#include +#include +#include +#include +#include +#include + +#define NTHREADS 8 +#define NITER 20000 +#define NROUNDS 5 + +static void *hammer(void *arg) +{ + long i; + (void)arg; + for (i = 0; i < NITER; i++) { + breakpoint_initialized = false; + breakpoint_init(); + } + return NULL; +} + +int main(void) +{ + int round, crashed = 0; + + alarm(60); + plan_tests(1); + + for (round = 0; round < NROUNDS && !crashed; round++) { + pthread_t th[NTHREADS]; + pid_t pid; + int status, i; + + pid = fork(); + if (pid == 0) { + for (i = 0; i < NTHREADS; i++) + pthread_create(&th[i], NULL, hammer, NULL); + for (i = 0; i < NTHREADS; i++) + pthread_join(th[i], NULL); + exit(0); + } + waitpid(pid, &status, 0); + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTRAP) + crashed = 1; + } + todo_start("breakpoint_init() thread race unresolved (audit F2)"); + ok1(!crashed); + todo_end(); + return exit_status(); +} diff --git a/ccan/ccan/build_assert/build_assert.h b/ccan/ccan/build_assert/build_assert.h index b9ecd84028e3..03ad7106a890 100644 --- a/ccan/ccan/build_assert/build_assert.h +++ b/ccan/ccan/build_assert/build_assert.h @@ -1,13 +1,19 @@ /* CC0 (Public domain) - see LICENSE file for details */ #ifndef CCAN_BUILD_ASSERT_H #define CCAN_BUILD_ASSERT_H +#include "config.h" /** * BUILD_ASSERT - assert a build-time dependency. * @cond: the compile-time condition which must be true. * - * Your compile will fail if the condition isn't true, or can't be evaluated - * by the compiler. This can only be used within a function. + * Your compile will fail if the condition isn't true. When the + * compiler supports C11 _Static_assert it will also fail if the + * condition can't be evaluated by the compiler; otherwise (older + * compilers) a non-constant condition is silently accepted (and is + * undefined behavior if false at runtime). + * + * This can only be used within a function. * * Example: * #include @@ -19,22 +25,38 @@ * return (char *)foo; * } */ +#if HAVE_STATIC_ASSERT +/* _Static_assert is a declaration, so do-while wrap avoids breaking if (x) BUILD_ASSERT... */ +#define BUILD_ASSERT(cond) \ + do { _Static_assert(cond, "BUILD_ASSERT"); } while(0) +#else #define BUILD_ASSERT(cond) \ do { (void) sizeof(char [1 - 2*!(cond)]); } while(0) +#endif /** * BUILD_ASSERT_OR_ZERO - assert a build-time dependency, as an expression. * @cond: the compile-time condition which must be true. * - * Your compile will fail if the condition isn't true, or can't be evaluated - * by the compiler. This can be used in an expression: its value is "0". + * Your compile will fail if the condition isn't true. When the + * compiler supports C11 _Static_assert it will also fail if the + * condition can't be evaluated by the compiler; otherwise (older + * compilers) a non-constant condition is silently accepted (and is + * undefined behavior if false at runtime). + * + * This can be used in an expression: its value is "0". * * Example: * #define foo_to_char(foo) \ * ((char *)(foo) \ * + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0)) */ +#if HAVE_STATIC_ASSERT +#define BUILD_ASSERT_OR_ZERO(cond) \ + (sizeof(struct { _Static_assert(cond, "BUILD_ASSERT_OR_ZERO"); char c; }) - 1) +#else #define BUILD_ASSERT_OR_ZERO(cond) \ (sizeof(char [1 - 2*!(cond)]) - 1) +#endif #endif /* CCAN_BUILD_ASSERT_H */ diff --git a/ccan/ccan/build_assert/test/compile_fail-expr-nonconst.c b/ccan/ccan/build_assert/test/compile_fail-expr-nonconst.c new file mode 100644 index 000000000000..598cc54ffacf --- /dev/null +++ b/ccan/ccan/build_assert/test/compile_fail-expr-nonconst.c @@ -0,0 +1,20 @@ +#include + +/* Regression test for audit finding F1, expression form: with a + * non-constant condition BUILD_ASSERT_OR_ZERO must fail compilation + * (build_assert.h:29-30). Today it compiles silently and, for a + * condition false at runtime, yields a garbage value (UB via a + * negative VLA bound) instead of the documented 0. */ +int main(int argc, char *argv[]) +{ + (void)argv; +#ifdef FAIL + return BUILD_ASSERT_OR_ZERO(argc == 0) == 0; +#if !HAVE_STATIC_ASSERT +#error "Unfortunately we don't fail on non-constant conditions without _Static_assert." +#endif +#else + (void)argc; + return 0; +#endif +} diff --git a/ccan/ccan/build_assert/test/compile_fail-nonconst.c b/ccan/ccan/build_assert/test/compile_fail-nonconst.c new file mode 100644 index 000000000000..b0b7a436a2ba --- /dev/null +++ b/ccan/ccan/build_assert/test/compile_fail-nonconst.c @@ -0,0 +1,26 @@ +#include + +/* Regression test for audit finding F1: build_assert.h:9-10 documents + * "Your compile will fail if the condition isn't true, or can't be + * evaluated by the compiler." With a non-constant condition the + * negative-array-size trick becomes a VLA bound, which gcc and clang + * both accept silently (no diagnostic even with -Wall -Wextra -Werror), + * so the assertion never fires; a false condition at runtime is a + * negative VLA bound, i.e. undefined behavior. + * + * Today this file compiles even with FAIL defined (the defect); after + * the repair (a constant-expression-forcing implementation, e.g. + * _Static_assert), the FAIL build must be rejected. */ +int main(int argc, char *argv[]) +{ + (void)argv; +#ifdef FAIL + BUILD_ASSERT(argc == 0); +#if !HAVE_STATIC_ASSERT +#error "Unfortunately we don't fail on non-constant conditions without _Static_assert." +#endif +#else + (void)argc; +#endif + return 0; +} diff --git a/ccan/ccan/cdump/cdump.c b/ccan/ccan/cdump/cdump.c index 7e42dbd74dc6..50419c5d763a 100644 --- a/ccan/ccan/cdump/cdump.c +++ b/ccan/ccan/cdump/cdump.c @@ -48,9 +48,10 @@ static struct token *tokenize(const void *ctx, const char *code) } else if (code[i] == '/' && code[i+1] == '*') { /* Multi-line comment. */ const char *end = strstr(code+i+2, "*/"); - len = (end + 2) - (code + i); if (!end) len = strlen(code + i); + else + len = (end + 2) - (code + i); if (tok_start != -1U) { add_token(&toks, code+tok_start, i - tok_start); tok_start = -1U; @@ -82,6 +83,10 @@ static struct token *tokenize(const void *ctx, const char *code) start_of_line = false; } + /* A trailing identifier running to EOF is still a token. */ + if (tok_start != -1U) + add_token(&toks, code+tok_start, i - tok_start); + /* Add terminating NULL. */ tal_resizez(&toks, tal_count(toks) + 1); return toks; @@ -92,6 +97,7 @@ struct parse_state { const struct token *toks; struct cdump_definitions *defs; char *complaints; + unsigned int depth; }; static const struct token *tok_peek(const struct token **toks) @@ -277,17 +283,28 @@ static void tok_take_unknown_statement(struct parse_state *ps) static bool tok_take_expr(struct parse_state *ps, const char *term) { + /* Recursion is one frame per nested ( or [: bound it. */ + if (ps->depth++ == 100) { + complain(ps, "Expression nested too deeply"); + goto fail; + } while (!tok_is(&ps->toks, term)) { if (tok_take_if(&ps->toks, "(")) { if (!tok_take_expr(ps, ")")) - return false; + goto fail; } else if (tok_take_if(&ps->toks, "[")) { if (!tok_take_expr(ps, "]")) - return false; + goto fail; } else if (!tok_take(&ps->toks)) - return false; + goto fail; } - return tok_take(&ps->toks); + if (!tok_take(&ps->toks)) + goto fail; + ps->depth--; + return true; +fail: + ps->depth--; + return false; } static char *tok_take_expr_str(const tal_t *ctx, @@ -347,7 +364,12 @@ static bool tok_take_type(struct parse_state *ps, struct cdump_type **type) /* Did we get some? */ if (ps->toks != types) { - name = string_of_toks(NULL, types, tok_peek(&ps->toks)); + const struct token *until = tok_peek(&ps->toks); + if (!until) { + complain(ps, "EOF after type"); + return false; + } + name = string_of_toks(NULL, types, until); kind = CDUMP_UNKNOWN; } else { /* Try normal types (or simple typedefs, etc). */ @@ -654,6 +676,7 @@ struct cdump_definitions *cdump_extract(const tal_t *ctx, const char *code, ps.defs = tal(ctx, struct cdump_definitions); ps.complaints = tal_strdup(ctx, ""); ps.code = code; + ps.depth = 0; strmap_init(&ps.defs->enums); strmap_init(&ps.defs->structs); diff --git a/ccan/ccan/cdump/test/run-eof-builtin-type.c b/ccan/ccan/cdump/test/run-eof-builtin-type.c new file mode 100644 index 000000000000..26ab90d32dd4 --- /dev/null +++ b/ccan/ccan/cdump/test/run-eof-builtin-type.c @@ -0,0 +1,39 @@ +/* Regression test: builtin type word as last token inside a struct/union + * body used to NULL-deref in string_of_toks() via tok_take_type() + * (cdump.c:350 passed tok_peek()==NULL as `until`). */ +#include +/* Include the C files directly. */ +#include +#include +#include + +int main(void) +{ + struct cdump_definitions *defs; + char *problems; + + plan_tests(8); + alarm(10); + + /* Truncated after a builtin type word: must fail gracefully. */ + defs = cdump_extract(NULL, "struct s { int ", &problems); + ok1(!defs); + ok1(problems); + + defs = cdump_extract(NULL, "struct s { unsigned ", &problems); + ok1(!defs); + ok1(problems); + + /* Same, but with a qualifier before the builtin type. */ + defs = cdump_extract(NULL, "union u { const long ", &problems); + ok1(!defs); + ok1(problems); + + /* Sanity: the complete version still parses. */ + defs = cdump_extract(NULL, "struct s { int x; };", &problems); + ok1(defs != NULL); + ok1(problems == NULL); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/cdump/test/run-expr-recursion.c b/ccan/ccan/cdump/test/run-expr-recursion.c new file mode 100644 index 000000000000..b4a93351cbb2 --- /dev/null +++ b/ccan/ccan/cdump/test/run-expr-recursion.c @@ -0,0 +1,44 @@ +/* Regression test: tok_take_expr() recursed once per nested '(' or '[' + * with no depth limit, so a deeply nested array size / CDUMP note / + * __attribute__ expression overflowed the stack (SIGSEGV). */ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include +#include + +int main(void) +{ + /* Keep the stack small so unbounded recursion dies quickly + * instead of after megabytes of input. */ + struct rlimit rl = { 256*1024, 256*1024 }; + size_t depth = 20000, i; + char *code, *p, *problems; + struct cdump_definitions *defs; + + plan_tests(2); + alarm(60); + + setrlimit(RLIMIT_STACK, &rl); + + code = tal_arr(NULL, char, depth * 2 + 64); + p = code; + p += sprintf(p, "struct s { int a["); + for (i = 0; i < depth; i++) + *p++ = '('; + *p++ = '1'; + for (i = 0; i < depth; i++) + *p++ = ')'; + p += sprintf(p, "]; };"); + + /* Must fail gracefully (or succeed), never crash. */ + defs = cdump_extract(NULL, code, &problems); + ok1(defs == NULL); + ok1(problems != NULL); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/cdump/test/run-trailing-ident.c b/ccan/ccan/cdump/test/run-trailing-ident.c new file mode 100644 index 000000000000..a3b7cb6ae461 --- /dev/null +++ b/ccan/ccan/cdump/test/run-trailing-ident.c @@ -0,0 +1,45 @@ +/* Regression test: tokenize() never flushed a trailing identifier token, + * so input ending in an identifier silently dropped it. "struct" alone + * was accepted as an empty, successful parse instead of a parse error. */ +#include +/* Include the C files directly. */ +#include +#include +#include + +int main(void) +{ + struct cdump_definitions *defs; + char *problems; + + plan_tests(10); + alarm(10); + + /* Truncated keywords must be parse errors, not silent success. */ + defs = cdump_extract(NULL, "struct", &problems); + ok1(!defs); + ok1(problems); + + defs = cdump_extract(NULL, "enum", &problems); + ok1(!defs); + ok1(problems); + + defs = cdump_extract(NULL, "union", &problems); + ok1(!defs); + ok1(problems); + + /* Trailing identifier after a complete definition must not be + * silently swallowed: "struct s { int x; }; struct t" is truncated + * and must complain. */ + defs = cdump_extract(NULL, "struct s { int x; }; struct t", &problems); + ok1(!defs); + ok1(problems); + + /* Sanity: trailing whitespace/punctuation forms are unaffected. */ + defs = cdump_extract(NULL, "enum foo { BAR };", &problems); + ok1(defs != NULL); + ok1(problems == NULL); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/cdump/test/run-unterminated-comment.c b/ccan/ccan/cdump/test/run-unterminated-comment.c new file mode 100644 index 000000000000..3cdd6c5dea99 --- /dev/null +++ b/ccan/ccan/cdump/test/run-unterminated-comment.c @@ -0,0 +1,30 @@ +/* Regression test: an unterminated C comment made tokenize() compute + * (end + 2) on a NULL end pointer before the !end check (cdump.c:51) — + * UB flagged by UBSan ("applying non-zero offset 2 to null pointer"). + * This only fails under UBSan; plain builds got the right result anyway + * because len is recomputed in the !end branch. */ +#include +/* Include the C files directly. */ +#include +#include +#include + +int main(void) +{ + struct cdump_definitions *defs; + char *problems; + + plan_tests(4); + alarm(10); + + defs = cdump_extract(NULL, "/*", &problems); + ok1(defs != NULL); + ok1(problems == NULL); + + defs = cdump_extract(NULL, "struct s { int x; }; /* unterminated", &problems); + ok1(defs != NULL); + ok1(problems == NULL); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/check_type/check_type.h b/ccan/ccan/check_type/check_type.h index 837aef7b1a36..a97d93d0a2b0 100644 --- a/ccan/ccan/check_type/check_type.h +++ b/ccan/ccan/check_type/check_type.h @@ -45,7 +45,15 @@ * ((encl_type *) \ * ((char *)(mbr_ptr) - offsetof(encl_type, mbr)))) */ -#if HAVE_TYPEOF +#if HAVE_TYPEOF && HAVE_BUILTIN_TYPES_COMPATIBLE_P +#include +#define check_type(expr, type) \ + BUILD_ASSERT_OR_ZERO(__builtin_types_compatible_p(typeof(expr), type)) + +#define check_types_match(expr1, expr2) \ + BUILD_ASSERT_OR_ZERO(__builtin_types_compatible_p(typeof(expr1), \ + typeof(expr2))) +#elif HAVE_TYPEOF #define check_type(expr, type) \ ((typeof(expr) *)0 != (type *)0) diff --git a/ccan/ccan/check_type/test/compile_fail-check_type_void.c b/ccan/ccan/check_type/test/compile_fail-check_type_void.c new file mode 100644 index 000000000000..c87cb6785c19 --- /dev/null +++ b/ccan/ccan/check_type/test/compile_fail-check_type_void.c @@ -0,0 +1,21 @@ +#include + +/* Regression test for audit finding F1 (2026-08-05): an expression of + * type void (e.g. a dereferenced void pointer, the shape container_of + * produces for a void * member pointer) silently bypasses check_type: + * (void *)0 != (int *)0 is constraint-conforming, so no diagnostic is + * issued at any warning level. The header documents "a warning or + * build failure if type is not correct". This test is RED until the + * check is strengthened (e.g. __builtin_types_compatible_p). */ +int main(int argc, char *argv[]) +{ + void *vp = argv; + (void)argc; +#ifdef FAIL + if (check_type(*vp, int)) + return 1; +#else + (void)vp; +#endif + return 0; +} diff --git a/ccan/ccan/check_type/test/compile_fail-check_types_match_void.c b/ccan/ccan/check_type/test/compile_fail-check_types_match_void.c new file mode 100644 index 000000000000..a4afe947e908 --- /dev/null +++ b/ccan/ccan/check_type/test/compile_fail-check_types_match_void.c @@ -0,0 +1,17 @@ +#include + +/* Regression test for audit finding F1 (2026-08-05): see + * compile_fail-check_type_void.c. check_types_match with one side of + * type void is likewise accepted silently. RED until repaired. */ +int main(int argc, char *argv[]) +{ + void *vp = argv; +#ifdef FAIL + if (check_types_match(*vp, argc)) + return 1; +#else + (void)vp; + (void)argc; +#endif + return 0; +} diff --git a/ccan/ccan/compiler/compiler.h b/ccan/ccan/compiler/compiler.h index 562b29ec71cc..cdba22aa460a 100644 --- a/ccan/ccan/compiler/compiler.h +++ b/ccan/ccan/compiler/compiler.h @@ -75,6 +75,7 @@ #else #define CONST_FUNCTION #endif +#endif #ifndef PURE_FUNCTION #if HAVE_ATTRIBUTE_PURE @@ -89,7 +90,6 @@ #define PURE_FUNCTION #endif #endif -#endif #if HAVE_ATTRIBUTE_UNUSED #ifndef UNNEEDED @@ -199,7 +199,7 @@ * // Use inline if compiler knows answer. Otherwise call function * // to avoid copies of the same code everywhere. * #define greek_name(g) \ - * (IS_COMPILE_CONSTANT(greek) ? _greek_name(g) : greek_name(g)) + * (IS_COMPILE_CONSTANT(g) ? _greek_name(g) : greek_name(g)) */ #define IS_COMPILE_CONSTANT(expr) __builtin_constant_p(expr) #else @@ -230,6 +230,7 @@ #endif +#ifndef WARN_DEPRECATED #if HAVE_ATTRIBUTE_DEPRECATED /** * WARN_DEPRECATED - warn that a function/type/variable is deprecated when used. @@ -243,8 +244,9 @@ #else #define WARN_DEPRECATED #endif +#endif - +#ifndef NO_NULL_ARGS #if HAVE_ATTRIBUTE_NONNULL /** * NO_NULL_ARGS - specify that no arguments to this function can be NULL. @@ -255,7 +257,13 @@ * NO_NULL_ARGS char *my_copy(char *buf); */ #define NO_NULL_ARGS __attribute__((__nonnull__)) +#else +#define NO_NULL_ARGS +#endif +#endif +#ifndef NON_NULL_ARGS +#if HAVE_ATTRIBUTE_NONNULL /** * NON_NULL_ARGS - specify that some arguments to this function can't be NULL. * @...: 1-based argument numbers for which args can't be NULL. @@ -267,10 +275,11 @@ */ #define NON_NULL_ARGS(...) __attribute__((__nonnull__(__VA_ARGS__))) #else -#define NO_NULL_ARGS #define NON_NULL_ARGS(...) #endif +#endif +#ifndef RETURNS_NONNULL #if HAVE_ATTRIBUTE_RETURNS_NONNULL /** * RETURNS_NONNULL - specify that this function cannot return NULL. @@ -284,7 +293,9 @@ #else #define RETURNS_NONNULL #endif +#endif +#ifndef LAST_ARG_NULL #if HAVE_ATTRIBUTE_SENTINEL /** * LAST_ARG_NULL - specify the last argument of a variadic function must be NULL. @@ -298,7 +309,9 @@ #else #define LAST_ARG_NULL #endif +#endif +#ifndef cpu_supports #if HAVE_BUILTIN_CPU_SUPPORTS /** * cpu_supports - test if current CPU supports the named feature. @@ -313,5 +326,6 @@ #else #define cpu_supports(x) 0 #endif /* HAVE_BUILTIN_CPU_SUPPORTS */ +#endif #endif /* CCAN_COMPILER_H */ diff --git a/ccan/ccan/compiler/test/run-predefine-const.c b/ccan/ccan/compiler/test/run-predefine-const.c new file mode 100644 index 000000000000..caf83ffc0d8e --- /dev/null +++ b/ccan/ccan/compiler/test/run-predefine-const.c @@ -0,0 +1,20 @@ +/* Regression test for compiler.h guard nesting: predefining CONST_FUNCTION + * (the purpose of the "#ifndef CONST_FUNCTION" guard) must not suppress the + * definition of PURE_FUNCTION. Today the PURE_FUNCTION block is nested + * inside the CONST_FUNCTION guard, so this file fails to compile until + * compiler.h is fixed (PURE_FUNCTION "unknown type name"). */ +#define CONST_FUNCTION +#include +#include + +static PURE_FUNCTION int double_it(int x) +{ + return x * 2; +} + +int main(void) +{ + plan_tests(1); + ok1(double_it(2) == 4); + return exit_status(); +} diff --git a/ccan/ccan/container_of/container_of.h b/ccan/ccan/container_of/container_of.h index 47a34d853b4c..0487f55c32a6 100644 --- a/ccan/ccan/container_of/container_of.h +++ b/ccan/ccan/container_of/container_of.h @@ -68,9 +68,10 @@ static inline char *container_of_or_null_(void *member_ptr, size_t offset) } #define container_of_or_null(member_ptr, containing_type, member) \ ((containing_type *) \ - container_of_or_null_(member_ptr, \ - container_off(containing_type, member)) \ - + check_types_match(*(member_ptr), ((containing_type *)0)->member)) + ((void)check_types_match(*(member_ptr), \ + ((containing_type *)0)->member), \ + container_of_or_null_(member_ptr, \ + container_off(containing_type, member)))) /** * container_off - get offset to enclosing structure diff --git a/ccan/ccan/container_of/test/run-or-null.c b/ccan/ccan/container_of/test/run-or-null.c new file mode 100644 index 000000000000..7b218178e493 --- /dev/null +++ b/ccan/ccan/container_of/test/run-or-null.c @@ -0,0 +1,35 @@ +#include +#include + +struct outer { + int first; + char pad[3]; + struct inner { + long x; + char c; + } second; +}; + +int main(void) +{ + struct outer o = { 0 }, *p; + + plan_tests(6); + + /* NULL member pointer, member at offset 0 and at non-zero offset. */ + ok1(container_of_or_null((int *)NULL, struct outer, first) == NULL); + ok1(container_of_or_null((struct inner *)NULL, struct outer, + second) == NULL); + + /* Non-NULL member pointer, member at offset 0 and non-zero offset. */ + p = container_of_or_null(&o.first, struct outer, first); + ok1(p == &o); + p = container_of_or_null(&o.second, struct outer, second); + ok1(p == &o); + + /* container_of on the same members for contrast. */ + ok1(container_of(&o.first, struct outer, first) == &o); + ok1(container_of(&o.second, struct outer, second) == &o); + + return exit_status(); +} diff --git a/ccan/ccan/cppmagic/cppmagic.h b/ccan/ccan/cppmagic/cppmagic.h index f1f6868e550d..dee273610f33 100644 --- a/ccan/ccan/cppmagic/cppmagic.h +++ b/ccan/ccan/cppmagic/cppmagic.h @@ -46,7 +46,7 @@ * expands to '1' if @a is '0', otherwise expands to '0'. */ #define _CPPMAGIC_ISPROBE(...) CPPMAGIC_2ND(__VA_ARGS__, 0) -#define _CPPMAGIC_PROBE() $, 1 +#define _CPPMAGIC_PROBE() _cppmagic_probe, 1 #define _CPPMAGIC_ISZERO_0 _CPPMAGIC_PROBE() #define CPPMAGIC_ISZERO(a_) \ _CPPMAGIC_ISPROBE(CPPMAGIC_GLUE2(_CPPMAGIC_ISZERO_, a_)) @@ -139,6 +139,11 @@ * * CPPMAGIC_MAP(@m, @a1, @a2, ... @an) * expands to the expansion of @m(@a1) , @m(@a2) , ... , @m(@an) + * + * Note: an argument which expands to no tokens is not supported: it + * silently truncates the argument list at that point (before C23 + * __VA_OPT__ there is no way to distinguish "expands to nothing" + * from "absent"). */ #define _CPPMAGIC_MAP_() _CPPMAGIC_MAP #define _CPPMAGIC_MAP(m_, a_, ...) \ @@ -158,6 +163,9 @@ * CPPMAGIC_2MAP(@m, @a1, @b1, @a2, @b2, ..., @an, @bn) * expands to the expansion of * @m(@a1, @b1) , @m(@a2, @b2) , ... , @m(@an, @bn) + * + * Note: an argument which expands to no tokens is not supported + * (see CPPMAGIC_MAP). */ #define _CPPMAGIC_2MAP_() _CPPMAGIC_2MAP #define _CPPMAGIC_2MAP(m_, a_, b_, ...) \ @@ -176,6 +184,9 @@ * * CPPMAGIC_JOIN(@d, @a1, @a2, ..., @an) * expands to the expansion of @a1 @d @a2 @d ... @d @an + * + * Note: an argument which expands to no tokens is not supported + * (see CPPMAGIC_MAP). */ #define _CPPMAGIC_JOIN_() _CPPMAGIC_JOIN #define _CPPMAGIC_JOIN(d_, a_, ...) \ diff --git a/ccan/ccan/cppmagic/test/run-map-empty-arg.c b/ccan/ccan/cppmagic/test/run-map-empty-arg.c new file mode 100644 index 000000000000..16ee82cdeeae --- /dev/null +++ b/ccan/ccan/cppmagic/test/run-map-empty-arg.c @@ -0,0 +1,47 @@ +/* Characterization test (2026-08-05 audit, see audit-findings/cppmagic.md + * F1): an argument which is textually empty or expands to no tokens + * silently terminates CPPMAGIC_MAP / CPPMAGIC_JOIN iteration, dropping + * all following arguments. Pre-C23 __VA_OPT__ the emptiness test + * cannot distinguish "expands to nothing" from "absent", so this is + * now the documented behavior (cppmagic.h CPPMAGIC_MAP doc note). + * This test pins it. */ +#include "config.h" + +#include +#include + +#include +#include + +static inline void check1(const char *orig, const char *expand, + const char *match) +{ + ok(strcmp(expand, match) == 0, + "%s => %s : %s", orig, expand, match); +} + +#define CHECK1(orig, match) \ + check1(#orig, CPPMAGIC_STRINGIFY(orig), match) + +#define TESTMAP(x) [x] + +/* EMPTY is an argument which expands to no tokens. */ +#define EMPTY + +int main(void) +{ + alarm(10); + + plan_tests(4); + + /* An expands-to-nothing argument reads as "absent": NONEMPTY + * reports 0, and iteration stops before it. */ + CHECK1(CPPMAGIC_NONEMPTY(EMPTY), "0"); + CHECK1(CPPMAGIC_MAP(TESTMAP, a, EMPTY, b), "[a]"); + CHECK1(CPPMAGIC_JOIN(;, a, EMPTY, b), "a"); + + /* A textually empty argument has the same behavior. */ + CHECK1(CPPMAGIC_MAP(TESTMAP, a, , b), "[a]"); + + return exit_status(); +} diff --git a/ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.c b/ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.c index f36bf67ade03..79d7ff434921 100644 --- a/ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.c +++ b/ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.c @@ -13,7 +13,10 @@ void hkdf_sha256(void *okm, size_t okm_size, struct hmac_sha256_ctx ctx; unsigned char c; - assert(okm_size < 255 * sizeof(t)); + assert(okm_size <= 255 * sizeof(t)); + + if (okm_size == 0) + return; /* RFC 5869: * diff --git a/ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.h b/ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.h index cf95c5afd8e7..b2905f1e6288 100644 --- a/ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.h +++ b/ccan/ccan/crypto/hkdf_sha256/hkdf_sha256.h @@ -7,7 +7,7 @@ /** * hkdf_sha256 - generate a derived key * @okm: where to output the key - * @okm_size: the number of bytes pointed to by @okm (must be less than 255*32) + * @okm_size: the number of bytes pointed to by @okm (must be at most 255*32) * @s: salt * @ssize: the number of bytes pointed to by @s * @k: pointer to input key diff --git a/ccan/ccan/crypto/hkdf_sha256/test/run-max-okm.c b/ccan/ccan/crypto/hkdf_sha256/test/run-max-okm.c new file mode 100644 index 000000000000..9ac1f6f086a2 --- /dev/null +++ b/ccan/ccan/crypto/hkdf_sha256/test/run-max-okm.c @@ -0,0 +1,57 @@ +/* Auditor-added regression test (temporary, 2026-08-05 audit). + * + * RFC 5869 section 2.3 permits L <= 255*HashLen = 8160 octets of OKM, + * but hkdf_sha256.c asserts okm_size < 255*32, rejecting the maximal + * legal L = 8160 (the implementation computes it correctly: the round + * counter reaches exactly 255). This test aborts on the assert against + * the current code and must pass once the bound is corrected to <=. + * + * Expected values: RFC5869 Appendix A Test Case 1 parameters; the + * 8160-octet OKM was produced by an independent Python hmac/hashlib + * HKDF-SHA256 implementation. Its first 42 octets necessarily equal + * the RFC's Test Case 1 OKM (HKDF-Expand is prefix-closed); the final + * 32 octets are T(255) = HMAC(PRK, T(254) | info | 0xff), proving the + * counter reached 255 without wrapping. + */ +#include +#include +#include +#include +#include + +static unsigned char okm[255 * 32]; + +int main(void) +{ + unsigned char ikm[22], salt[13], info[10]; + unsigned char expect[42]; + size_t i; + + alarm(10); + plan_tests(2); + + for (i = 0; i < sizeof(ikm); i++) + ikm[i] = 0x0b; + for (i = 0; i < sizeof(salt); i++) + salt[i] = i; + for (i = 0; i < sizeof(info); i++) + info[i] = 0xf0 + i; + + /* L = 8160 = 255*HashLen: the largest L RFC 5869 allows. */ + hkdf_sha256(okm, sizeof(okm), salt, sizeof(salt), ikm, sizeof(ikm), + info, sizeof(info)); + + /* Prefix equals RFC5869 Test Case 1 OKM (42 octets). */ + hex_decode("3cb25f25faacd57a90434f64d0362f2a" + "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" + "34007208d5b887185865", 84, expect, sizeof(expect)); + ok1(memcmp(okm, expect, sizeof(expect)) == 0); + + /* Final block is T(255) per independent reference. */ + hex_decode("76a3f78bcffe95fecf91923c22ad6ee6" + "4d48a6d1b981d7e523d5c0f22154ee88", 64, + expect, 32); + ok1(memcmp(okm + sizeof(okm) - 32, expect, 32) == 0); + + return exit_status(); +} diff --git a/ccan/ccan/crypto/sha256/sha256.h b/ccan/ccan/crypto/sha256/sha256.h index 9a310b9564c6..a7a0e86fd97b 100644 --- a/ccan/ccan/crypto/sha256/sha256.h +++ b/ccan/ccan/crypto/sha256/sha256.h @@ -49,7 +49,8 @@ struct sha256_ctx { uint32_t u32[16]; unsigned char u8[64]; } buf; - size_t bytes; + /* uint64_t: hashing 4GB+ must not wrap the length on 32-bit. */ + uint64_t bytes; #endif }; diff --git a/ccan/ccan/crypto/sha256/test/run-length-wrap.c b/ccan/ccan/crypto/sha256/test/run-length-wrap.c new file mode 100644 index 000000000000..abc0ffc3a8b2 --- /dev/null +++ b/ccan/ccan/crypto/sha256/test/run-length-wrap.c @@ -0,0 +1,52 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include + +/* Regression test for the 32-bit length-counter wrap (audit F2): + * struct sha256_ctx.bytes is a size_t. On ILP32 platforms it wraps + * after 2^32 bytes hashed, so sha256_done() emits a length field of + * (wrapped bytes) << 3 instead of the true bit count, and every + * message of 4 GiB or more gets a wrong digest (2^32 bytes is 2^35 + * bits, well inside SHA-256's 2^64-bit limit). + * + * Seeding a raw context like run-33-bit-test.c does: set bytes to + * 2^32 - 64, then update with 64 known bytes. A correct 64-bit + * counter yields the length field 2^35 and the digest below + * (independently reproduced with a reference SHA-256 implementation + * using an injected length field); a wrapped 32-bit size_t counter + * yields a length field of 0 and a different digest. + * + * Passes on LP64 today, fails on ILP32 (-m32) today; must pass + * everywhere after the repair. */ +static const unsigned char expected[32] = { + 0xfc, 0x48, 0x52, 0x1c, 0x03, 0xa8, 0xd0, 0x5c, + 0xfb, 0x68, 0x4c, 0x07, 0x7e, 0xf7, 0x7b, 0x6a, + 0x14, 0x56, 0x65, 0x4b, 0x75, 0xe3, 0xe5, 0x1c, + 0x1e, 0x9d, 0xe6, 0x84, 0x84, 0xf9, 0x4c, 0x87 +}; + +int main(void) +{ + struct sha256_ctx ctx = SHA256_INIT; + struct sha256 h; + unsigned char block[64]; + unsigned i; + + alarm(10); + plan_tests(1); + + for (i = 0; i < sizeof(block); i++) + block[i] = (unsigned char)(i * 3 + 1); + + /* == 2^32 - 64, representable in both ILP32 and LP64 size_t. */ + ctx.bytes = (size_t)0xFFFFFFC0ULL; + sha256_update(&ctx, block, sizeof(block)); + sha256_done(&ctx, &h); + + ok1(memcmp(h.u.u8, expected, sizeof(expected)) == 0); + + return exit_status(); +} diff --git a/ccan/ccan/crypto/shachain/shachain.c b/ccan/ccan/crypto/shachain/shachain.c index 9cb54a37bdd9..1bf896a8a180 100644 --- a/ccan/ccan/crypto/shachain/shachain.c +++ b/ccan/ccan/crypto/shachain/shachain.c @@ -86,8 +86,17 @@ bool shachain_add_hash(struct shachain *chain, /* You have to insert them in order! */ assert(index == shachain_next_index(chain)); + /* Reject out-of-domain indices (SHACHAIN_BITS < 64 builds, and + * the post-exhaustion wrap to UINT64_MAX). */ + if (index > (UINT64_MAX >> (64 - SHACHAIN_BITS))) + return false; + pos = count_trailing_zeroes(index); + /* Beyond the chain domain (past exhaustion): no such slot. */ + if (pos > SHACHAIN_BITS) + return false; + /* All derivable answers must be valid. */ /* FIXME: Is it sufficient to check just the next answer? */ for (i = 0; i < pos; i++) { diff --git a/ccan/ccan/crypto/shachain/test/run-exhaust-wrap.c b/ccan/ccan/crypto/shachain/test/run-exhaust-wrap.c new file mode 100644 index 000000000000..dc8e171e3d01 --- /dev/null +++ b/ccan/ccan/crypto/shachain/test/run-exhaust-wrap.c @@ -0,0 +1,83 @@ +/* Regression test (auditor-added, temporary): with SHACHAIN_BITS < 64, + * following the documented rule "You can only add shachain_next_index(@chain)" + * past chain exhaustion (index 0) wraps next_index to UINT64_MAX; continuing + * reaches an index whose count_trailing_zeroes() exceeds SHACHAIN_BITS, + * and shachain_add_hash writes known[pos] out of bounds. + * + * Against current code the canary after struct shachain is clobbered + * (and ASan reports a stack-buffer-overflow in shachain_add_hash). + * Must pass once additions beyond the chain domain are rejected. + */ +#define SHACHAIN_BITS 8 + +#include +/* Include the C files directly. */ +#include +#include + +#include +#include + +#define CANARY 0xDEADBEEFCAFEF00DULL + +int main(void) +{ + struct { + struct shachain chain; + uint64_t canary; + } w; + struct sha256 seed, h; + struct sha256 expect[256]; + uint64_t i; + unsigned int n; + + alarm(10); + plan_tests(4); + + memset(&seed, 0xA5, sizeof(seed)); + for (i = 0; i < 256; i++) + shachain_from_seed(&seed, i, &expect[i]); + + shachain_init(&w.chain); + w.canary = CANARY; + + ok1(shachain_next_index(&w.chain) == 255); + + /* Exhaust the chain: 255 down to 0, obeying next_index. */ + for (i = 255, n = 0; ; i--) { + if (shachain_next_index(&w.chain) != i) + break; + shachain_from_seed(&seed, i, &h); + if (!shachain_add_hash(&w.chain, i, &h)) + break; + n++; + if (i == 0) + break; + } + ok1(n == 256); + + /* All values still derivable. */ + for (i = 0, n = 0; i < 256; i++) { + if (shachain_get_hash(&w.chain, i, &h) + && memcmp(&h, &expect[i], sizeof(h)) == 0) + n++; + } + ok1(n == 256); + + /* Keep obeying "add shachain_next_index(@chain)": next_index wrapped + * to UINT64_MAX. At index 0xFFFFFFFFFFFFFE00 (511 steps on), + * count_trailing_zeroes(index) == 9 > SHACHAIN_BITS and the + * unguarded known[pos] write lands past the array. A repaired + * module rejects the out-of-domain addition instead. */ + for (i = 0; i < 600; i++) { + uint64_t idx = shachain_next_index(&w.chain); + shachain_from_seed(&seed, idx, &h); + if (!shachain_add_hash(&w.chain, idx, &h)) + break; + if (w.canary != CANARY) + break; + } + ok1(w.canary == CANARY); + + return exit_status(); +} diff --git a/ccan/ccan/err/test/run.c b/ccan/ccan/err/test/run.c index aeaa3750b3d4..d4ebbeae5c7c 100644 --- a/ccan/ccan/err/test/run.c +++ b/ccan/ccan/err/test/run.c @@ -30,7 +30,6 @@ int main(int argc, char *argv[]) /* Test err() in child */ if (pipe(pfd)) abort(); - fflush(stdout); if (fork()) { char buffer[BUFFER_MAX+1]; unsigned int i; @@ -63,7 +62,6 @@ int main(int argc, char *argv[]) /* Test errx() in child */ if (pipe(pfd)) abort(); - fflush(stdout); if (fork()) { char buffer[BUFFER_MAX+1]; unsigned int i; @@ -94,7 +92,6 @@ int main(int argc, char *argv[]) /* Test warn() in child */ if (pipe(pfd)) abort(); - fflush(stdout); if (fork()) { char buffer[BUFFER_MAX+1]; unsigned int i; @@ -127,7 +124,6 @@ int main(int argc, char *argv[]) /* Test warnx() in child */ if (pipe(pfd)) abort(); - fflush(stdout); if (fork()) { char buffer[BUFFER_MAX+1]; unsigned int i; diff --git a/ccan/ccan/fdpass/fdpass.c b/ccan/ccan/fdpass/fdpass.c index af1a7fdb9ab0..c64ccc11ce8a 100644 --- a/ccan/ccan/fdpass/fdpass.c +++ b/ccan/ccan/fdpass/fdpass.c @@ -4,6 +4,7 @@ #include #include #include +#include bool fdpass_send(int sockout, int fd) { @@ -71,11 +72,24 @@ int fdpass_recv(int sockin) return -1; cmsg = CMSG_FIRSTHDR(&msg); - if (!cmsg - || cmsg->cmsg_len != CMSG_LEN(sizeof(fd)) + if (!cmsg || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { - errno = -EINVAL; + errno = EINVAL; + return -1; + } + + if (cmsg->cmsg_len != CMSG_LEN(sizeof(fd))) { + /* The kernel already installed any fds the message + * carried; don't leak them. */ + if (cmsg->cmsg_len >= CMSG_LEN(0) + && cmsg->cmsg_len <= msg.msg_controllen) { + int *fds = (int *)CMSG_DATA(cmsg); + size_t i, nfds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + for (i = 0; i < nfds; i++) + close(fds[i]); + } + errno = EINVAL; return -1; } diff --git a/ccan/ccan/fdpass/test/run-errno.c b/ccan/ccan/fdpass/test/run-errno.c new file mode 100644 index 000000000000..439a0c19b25d --- /dev/null +++ b/ccan/ccan/fdpass/test/run-errno.c @@ -0,0 +1,44 @@ +/* Temporary auditor regression test (audit 2026-08-05). + * Proves: fdpass_recv sets errno = -EINVAL instead of EINVAL when the + * peer's message carries no (valid) SCM_RIGHTS control message. + * fdpass.h:20 documents "On failure, returns -1 and sets errno."; + * errno values must be positive (EINVAL == 22, not -22). + * Currently FAILS (errno == -EINVAL); must pass after repair. */ +#include +/* Include the C files directly. */ +#include +#include + +#include +#include +#include +#include +#include + +int main(void) +{ + int sv[2]; + + alarm(10); + plan_tests(5); + + ok1(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0); + + /* Case 1: plain data byte, no ancillary data at all. */ + ok1(write(sv[1], "1", 1) == 1); + errno = 0; + ok1(fdpass_recv(sv[0]) == -1 && errno == EINVAL); + + /* Case 2: orderly shutdown, nothing sent (recvmsg returns 0). */ + close(sv[0]); + close(sv[1]); + ok1(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0); + close(sv[1]); + errno = 0; + ok1(fdpass_recv(sv[0]) == -1 && errno == EINVAL); + + close(sv[0]); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/fdpass/test/run-multifd-leak.c b/ccan/ccan/fdpass/test/run-multifd-leak.c new file mode 100644 index 000000000000..b1d281fde2cf --- /dev/null +++ b/ccan/ccan/fdpass/test/run-multifd-leak.c @@ -0,0 +1,100 @@ +/* Temporary auditor regression test (audit 2026-08-05). + * Proves: when the peer sends TWO fds in ONE SCM_RIGHTS cmsg + * (cmsg_len = CMSG_LEN(2*sizeof(int)), which fits the receiver's + * CMSG_SPACE(sizeof(int)) control buffer), fdpass_recv rejects the + * message but the kernel has already installed both fds into this + * process; fdpass_recv never closes them -> fd leak (observed: 32 fds + * leaked over 16 rejected recvs). Repeatable by any non-fdpass peer; + * resource-exhaustion DoS. + * Currently FAILS; must pass after repair (reject path must close any + * fds carried by a malformed SCM_RIGHTS cmsg). */ +#include +/* Include the C files directly. */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int count_fds(void) +{ + DIR *d = opendir("/proc/self/fd"); + struct dirent *de; + int n = 0; + + if (!d) + return -1; + while ((de = readdir(d)) != NULL) { + if (de->d_name[0] != '.') + n++; + } + closedir(d); + return n - 1; /* exclude the fd used by opendir itself */ +} + +static void send_two_fds(int sock, int fd1, int fd2) +{ + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + struct iovec iov; + char c = 0; + union { + char buf[CMSG_SPACE(2 * sizeof(int))]; + struct cmsghdr align; + } u; + int fds[2] = { fd1, fd2 }; + + memset(&u, 0, sizeof(u)); + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(2 * sizeof(int)); + memcpy(CMSG_DATA(cmsg), fds, sizeof(fds)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + iov.iov_base = &c; + iov.iov_len = 1; + sendmsg(sock, &msg, 0); +} + +int main(void) +{ + int sv[2]; + int pfds[2]; + int before, after, i; + + alarm(10); + plan_tests(5); + + ok1(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0); + ok1(pipe(pfds) == 0); + ok1((before = count_fds()) >= 0); + + for (i = 0; i < 16; i++) + send_two_fds(sv[1], pfds[0], pfds[0]); + for (i = 0; i < 16; i++) { + if (fdpass_recv(sv[0]) != -1) + break; + } + ok1(i == 16); + + after = count_fds(); + ok(after == before, + "no fds leaked by rejected recvs (before=%d after=%d, leaked=%d)", + before, after, after - before); + + close(pfds[0]); + close(pfds[1]); + close(sv[0]); + close(sv[1]); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/htable/htable.c b/ccan/ccan/htable/htable.c index 0b515b94bbf0..c28a36b4260c 100644 --- a/ccan/ccan/htable/htable.c +++ b/ccan/ccan/htable/htable.c @@ -86,6 +86,12 @@ void htable_init(struct htable *ht, ht->table = &ht->common_bits; } +/* Number of buckets in the table. */ +static inline size_t ht_size(const struct htable *ht) +{ + return (size_t)1 << ht->bits; +} + /* Fill to 87.5% */ static inline size_t ht_max(const struct htable *ht) { @@ -95,7 +101,7 @@ static inline size_t ht_max(const struct htable *ht) /* Clean deleted if we're full, and more than 12.5% deleted */ static inline size_t ht_max_deleted(const struct htable *ht) { - return ((size_t)1 << ht->bits) / 8; + return ht_size(ht) / 8; } bool htable_init_sized(struct htable *ht, @@ -108,11 +114,15 @@ bool htable_init_sized(struct htable *ht, for (ht->bits = 1; ht_max(ht) < expect; ht->bits++) { if (ht->bits == 30) break; + /* Stop before the allocation size wraps (eg. 32-bit). */ + if ((sizeof(size_t) << (ht->bits + 1)) == 0) + break; } ht->table = htable_alloc(ht, sizeof(size_t) << ht->bits); if (!ht->table) { ht->table = &ht->common_bits; + ht->bits = 0; return false; } (void)htable_debug(ht, HTABLE_LOC); @@ -153,7 +163,7 @@ void htable_unlock(struct htable *ht) static size_t hash_bucket(const struct htable *ht, size_t h) { - return h & ((1 << ht->bits)-1); + return h & (ht_size(ht)-1); } static void *htable_val(const struct htable *ht, @@ -166,7 +176,7 @@ static void *htable_val(const struct htable *ht, if (get_extra_ptr_bits(ht, ht->table[i->off]) == h2) return get_raw_ptr(ht, ht->table[i->off]); } - i->off = (i->off + 1) & ((1 << ht->bits)-1); + i->off = (i->off + 1) & (ht_size(ht)-1); h2 &= ~perfect; } return NULL; @@ -182,13 +192,13 @@ void *htable_firstval_(const struct htable *ht, void *htable_nextval_(const struct htable *ht, struct htable_iter *i, size_t hash) { - i->off = (i->off + 1) & ((1 << ht->bits)-1); + i->off = (i->off + 1) & (ht_size(ht)-1); return htable_val(ht, i, hash, 0); } void *htable_first_(const struct htable *ht, struct htable_iter *i) { - for (i->off = 0; i->off < (size_t)1 << ht->bits; i->off++) { + for (i->off = 0; i->off < ht_size(ht); i->off++) { if (entry_is_valid(ht->table[i->off])) return get_raw_ptr(ht, ht->table[i->off]); } @@ -197,7 +207,7 @@ void *htable_first_(const struct htable *ht, struct htable_iter *i) void *htable_next_(const struct htable *ht, struct htable_iter *i) { - for (i->off++; i->off < (size_t)1 << ht->bits; i->off++) { + for (i->off++; i->off < ht_size(ht); i->off++) { if (entry_is_valid(ht->table[i->off])) return get_raw_ptr(ht, ht->table[i->off]); } @@ -243,7 +253,7 @@ static COLD void fixup_table_common(struct htable *ht, uintptr_t maskdiff) again: bitsdiff = ht->common_bits & maskdiff; - for (i = 0; i < (size_t)1 << ht->bits; i++) { + for (i = 0; i < ht_size(ht); i++) { uintptr_t e; if (!entry_is_valid(e = ht->table[i])) continue; @@ -296,7 +306,7 @@ static void ht_add(struct htable *ht, const void *new, size_t h) while (entry_is_valid(ht->table[i])) { perfect = 0; - i = (i + 1) & ((1 << ht->bits)-1); + i = (i + 1) & (ht_size(ht)-1); } ht->table[i] = make_hval(ht, new, get_hash_ptr_bits(ht, h)|perfect); if (!entry_is_valid(ht->table[i])) @@ -306,11 +316,16 @@ static void ht_add(struct htable *ht, const void *new, size_t h) static COLD bool double_table(struct htable *ht) { unsigned int i; - size_t oldnum = (size_t)1 << ht->bits; + size_t oldnum = ht_size(ht); + size_t newsize = sizeof(size_t) << (ht->bits+1); uintptr_t *oldtable, e; + /* 32-bit: doubling can wrap the allocation size to 0. */ + if (newsize == 0) + return false; + oldtable = ht->table; - ht->table = htable_alloc(ht, sizeof(size_t) << (ht->bits+1)); + ht->table = htable_alloc(ht, newsize); if (!ht->table) { ht->table = oldtable; return false; @@ -350,8 +365,8 @@ static COLD void rehash_table(struct htable *ht) /* Beware wrap cases: we need to start from first empty bucket. */ for (start = 0; ht->table[start]; start++); - for (i = 0; i < (size_t)1 << ht->bits; i++) { - size_t h = (i + start) & ((1 << ht->bits)-1); + for (i = 0; i < ht_size(ht); i++) { + size_t h = (i + start) & (ht_size(ht)-1); e = ht->table[h]; if (!e) continue; @@ -427,7 +442,7 @@ bool htable_del_(struct htable *ht, size_t h, const void *p) void htable_delval_(struct htable *ht, struct htable_iter *i) { - assert(i->off < (size_t)1 << ht->bits); + assert(i->off < ht_size(ht)); assert(entry_is_valid(ht->table[i->off])); ht->elems--; @@ -446,7 +461,7 @@ void *htable_pick_(const struct htable *ht, size_t seed, struct htable_iter *i) if (!i) i = &unwanted; - i->off = seed % ((size_t)1 << ht->bits); + i->off = seed % ht_size(ht); e = htable_next(ht, i); if (!e) e = htable_first(ht, i); diff --git a/ccan/ccan/htable/test/run-init-sized-alloc-fail.c b/ccan/ccan/htable/test/run-init-sized-alloc-fail.c new file mode 100644 index 000000000000..176dea0b60ba --- /dev/null +++ b/ccan/ccan/htable/test/run-init-sized-alloc-fail.c @@ -0,0 +1,79 @@ +/* Regression test: htable_init_sized() allocation-failure path must leave + * the htable in a consistent (empty) state, as documented: + * "If this returns false, @ht is still usable" (htable.h). + * + * Before the fix, ht->bits was left at the computed value while ht->table + * pointed at the singleton &ht->common_bits, so the next htable_add() + * indexed up to (1< +#include +#include +#include +#include +#include +#include + +static size_t hash(const void *elem, void *unused UNNEEDED) +{ + return *(size_t *)elem; +} + +static bool cmp(const void *candidate, void *ptr) +{ + return *(const size_t *)candidate == *(const size_t *)ptr; +} + +/* Fail any allocation larger than 64 bytes. */ +static void *fail_alloc(struct htable *ht, size_t len) +{ + if (len > 64) + return NULL; + return calloc(len, 1); +} + +static void fail_free(struct htable *ht, void *p) +{ + free(p); +} + +static void timeout_handler(int sig) +{ + (void)sig; + _exit(1); +} + +int main(void) +{ + struct htable ht; + size_t val = 3; + + alarm(10); + signal(SIGALRM, timeout_handler); + + plan_tests(5); + htable_set_allocator(fail_alloc, fail_free); + + /* expect=1000 -> bits=11 -> needs 16KB -> allocation fails. */ + ok1(!htable_init_sized(&ht, hash, NULL, 1000)); + + /* Must be a consistent empty table: singleton table means bits == 0. */ + ok1(ht.table == &ht.common_bits); + ok1(ht.bits == 0); + + if (ht.bits == 0) { + /* Documented as still usable: this must not corrupt memory. */ + ok1(htable_add(&ht, hash(&val, NULL), &val)); + ok1(htable_get(&ht, hash(&val, NULL), cmp, &val) == &val); + } else { + /* Adding now would write out of bounds; fail safely. */ + ok(0, "htable unusable after failed htable_init_sized (bits=%u)", + ht.bits); + ok(0, "skipping add which would write out of bounds"); + } + + htable_clear(&ht); + htable_set_allocator(NULL, NULL); + return exit_status(); +} diff --git a/ccan/ccan/htable/test/run-init-sized-huge.c b/ccan/ccan/htable/test/run-init-sized-huge.c new file mode 100644 index 000000000000..06cac1e054c2 --- /dev/null +++ b/ccan/ccan/htable/test/run-init-sized-huge.c @@ -0,0 +1,79 @@ +/* Regression test: htable_init_sized() must not overflow its allocation + * size computation. With expect large enough to reach the bits==30 cap, + * the size was computed as sizeof(size_t) << 30; on 32-bit (ILP32) that + * wraps to 0, so a 0-byte allocation "succeeds", the function returns + * true, and the first htable_add() writes out of bounds (ASan: + * heap-buffer-overflow in ht_add()). + * + * The fix stops the sizing loop before the allocation size wraps (on + * ILP32 that caps bits at 29). This test intercepts the allocator and + * checks the requested size against the overflow-free computation, so it + * fails on 32-bit before the fix and passes everywhere after it. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +static size_t hash(const void *elem, void *unused UNNEEDED) +{ + return *(size_t *)elem; +} + +static size_t requested_len; +static bool saw_request; + +/* Record the request; fail anything larger than 1MB. */ +static void *spy_alloc(struct htable *ht, size_t len) +{ + requested_len = len; + saw_request = true; + if (len > 1048576) + return NULL; + return calloc(len, 1); +} + +static void spy_free(struct htable *ht, void *p) +{ + free(p); +} + +static void timeout_handler(int sig) +{ + (void)sig; + _exit(1); +} + +int main(void) +{ + struct htable ht; + /* ht_max(bits=29) == 469762048 on both ILP32 and LP64, so this + * expect drives the sizing loop to its cap. */ + const size_t expect = (size_t)469762048 + 1; + /* Overflow-free computation of the correct request: bits==30 on + * LP64, capped at bits==29 on ILP32 where 4 << 30 would wrap. */ + uint64_t correct = (uint64_t)sizeof(size_t) << 30; + + if (correct > (uint64_t)SIZE_MAX) + correct = (uint64_t)sizeof(size_t) << 29; + + alarm(10); + signal(SIGALRM, timeout_handler); + + plan_tests(2); + htable_set_allocator(spy_alloc, spy_free); + + htable_init_sized(&ht, hash, NULL, expect); + ok1(saw_request); + ok((uint64_t)requested_len == correct, + "alloc size at sizing cap: requested %zu, correct %llu", + requested_len, (unsigned long long)correct); + + htable_clear(&ht); + htable_set_allocator(NULL, NULL); + return exit_status(); +} diff --git a/ccan/ccan/ilog/ilog.h b/ccan/ccan/ilog/ilog.h index 32702b178567..960e89f78fa3 100644 --- a/ccan/ccan/ilog/ilog.h +++ b/ccan/ccan/ilog/ilog.h @@ -131,7 +131,7 @@ int ilog64_nz(uint64_t _v) CONST_FUNCTION; #endif /* builtin_ilog32_nz */ #ifdef builtin_ilog64_nz -#define ilog32(_v) ((_v) ? builtin_ilog32_nz(_v) : 0) +#define ilog64(_v) ((_v) ? builtin_ilog64_nz(_v) : 0) #define ilog64_nz(_v) builtin_ilog64_nz(_v) #else #define ilog64_nz(_v) ilog64(_v) diff --git a/ccan/ccan/intmap/intmap.h b/ccan/ccan/intmap/intmap.h index 834c969fa7c7..6cc4c59bd44b 100644 --- a/ccan/ccan/intmap/intmap.h +++ b/ccan/ccan/intmap/intmap.h @@ -467,7 +467,8 @@ static inline void *sintmap_first_(const struct intmap *map, { intmap_index_t i; void *ret = intmap_first_(map, &i); - *indexp = SINTMAP_UNOFF(i); + if (ret) + *indexp = SINTMAP_UNOFF(i); return ret; } @@ -495,7 +496,8 @@ static inline void *sintmap_last_(const struct intmap *map, { intmap_index_t i; void *ret = intmap_last_(map, &i); - *indexp = SINTMAP_UNOFF(i); + if (ret) + *indexp = SINTMAP_UNOFF(i); return ret; } diff --git a/ccan/ccan/intmap/test/run-signed-firstlast-empty.c b/ccan/ccan/intmap/test/run-signed-firstlast-empty.c new file mode 100644 index 000000000000..dbba284761f3 --- /dev/null +++ b/ccan/ccan/intmap/test/run-signed-firstlast-empty.c @@ -0,0 +1,56 @@ +/* Regression test: sintmap_first()/sintmap_last() on an empty map must + * not populate *indexp (documented: "Returns NULL if the map is empty, + * otherwise populates *@indexp"). The current implementation always + * writes SINTMAP_UNOFF(i) where i is an uninitialized local when the + * map is empty (valgrind: "Conditional jump or move depends on + * uninitialised value(s)"), clobbering the caller's index variable + * with garbage. uintmap_first/uintmap_last leave *indexp untouched. + */ +#include +#include +#include +#include + +#define SENTINEL 12345 + +/* Groom the stack so the uninitialized read picks up a known-bad value + * rather than something that happens to equal SENTINEL. */ +static void groom_stack(void) +{ + volatile uint64_t pad[32]; + + for (size_t i = 0; i < sizeof(pad)/sizeof(pad[0]); i++) + pad[i] = 0xDEADBEEFCAFEBABEULL; +} + +int main(void) +{ + SINTMAP(const char *) map; + sintmap_index_t s; + const char *r; + + alarm(10); + + plan_tests(6); + sintmap_init(&map); + + /* First/last on empty map return NULL... */ + groom_stack(); + s = SENTINEL; + r = sintmap_first(&map, &s); + ok1(r == NULL); + ok1(errno == ENOENT); + /* ...and must not touch *indexp. */ + ok1(s == SENTINEL); + + groom_stack(); + s = SENTINEL; + r = sintmap_last(&map, &s); + ok1(r == NULL); + ok1(errno == ENOENT); + /* ...and must not touch *indexp. */ + ok1(s == SENTINEL); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/io/io.c b/ccan/ccan/io/io.c index baa9a497cc56..c4ad9cac5898 100644 --- a/ccan/ccan/io/io.c +++ b/ccan/ccan/io/io.c @@ -33,6 +33,11 @@ struct io_listener *io_new_listener_(const tal_t *ctx, int fd, l->ctx = ctx; if (!add_listener(l)) return tal_free(l); + + /* Keep accept() async: a connection which vanishes between + * poll() and accept() (eg. peer RST) must not block the loop. */ + io_fd_block(fd, false); + return l; } diff --git a/ccan/ccan/io/poll.c b/ccan/ccan/io/poll.c index c4cbaee85678..dd21dbc1e6e3 100644 --- a/ccan/ccan/io/poll.c +++ b/ccan/ccan/io/poll.c @@ -310,6 +310,18 @@ static bool handle_always(void) return false; } +/* Is there an always plan we can actually run right now? */ +static bool always_runnable(void) +{ + size_t i; + + for (i = 0; i < num_always; i++) { + if (!num_exclusive || *exclusive(always[i])) + return true; + } + return false; +} + bool backend_set_exclusive(struct io_plan *plan, bool excl) { bool *excl_ptr = exclusive(plan); @@ -374,7 +386,7 @@ void *io_loop(struct timers *timers, struct timer **expired) { void *ret; /* This ensures we don't always service lower fds first */ - static int fairness_counter; + static size_t fairness_counter; /* if timers is NULL, expired must be. If not, not. */ assert(!timers == !expired); @@ -415,7 +427,7 @@ void *io_loop(struct timers *timers, struct timer **expired) } /* Don't wait if we have always requests pending! */ - if (num_always != 0) + if (always_runnable()) ms_timeout = 0; /* We do this temporarily, assuming exclusive is unusual */ diff --git a/ccan/ccan/io/test/run-49-exclusive-always-spin.c b/ccan/ccan/io/test/run-49-exclusive-always-spin.c new file mode 100644 index 000000000000..f477fc526824 --- /dev/null +++ b/ccan/ccan/io/test/run-49-exclusive-always-spin.c @@ -0,0 +1,86 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include +#include + +/* Regression test: an exclusive io_conn plus a pending non-exclusive + * always plan must not make io_loop spin on poll() with timeout 0. + * The always plan may not run (documented), but the loop should block + * on the exclusive conn's fd instead of busy-polling. */ + +static int b_wait, a_wait; +static int armed; +static unsigned int zero_timeout_polls; +static int good, bad; + +static int checking_poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + if (armed) { + if (timeout == 0) { + /* Spinning: always plans pending but none runnable. */ + if (++zero_timeout_polls > 100) + io_break(&bad); + } else { + /* Correct: blocking poll (or finite timer). */ + io_break(&good); + return 0; + } + } + return poll(fds, nfds, timeout); +} + +static struct io_plan *b_woken(struct io_conn *conn, void *unused) +{ + /* Must not run while the other conn is exclusive. */ + return io_close(conn); +} + +static struct io_plan *init_b(struct io_conn *conn, void *unused) +{ + return io_wait(conn, &b_wait, b_woken, NULL); +} + +static struct io_plan *a_got_data(struct io_conn *conn, char *buf) +{ + io_conn_exclusive(conn, true); + /* Queue B's always plan (non-exclusive, cannot run). */ + io_wake(&b_wait); + armed = 1; + /* Sleep forever. */ + return io_wait(conn, &a_wait, io_never, NULL); +} + +static struct io_plan *init_a(struct io_conn *conn, char *buf) +{ + return io_read(conn, buf, 1, a_got_data, buf); +} + +int main(void) +{ + int afd[2], bfd[2]; + char buf[16]; + void *ret; + + plan_tests(3); + alarm(10); + + ok1(pipe(afd) == 0); + ok1(pipe(bfd) == 0); + + io_poll_override(checking_poll); + + io_new_conn(NULL, afd[0], init_a, buf); + io_new_conn(NULL, bfd[0], init_b, NULL); + + if (write(afd[1], "x", 1) != 1) + exit(1); + + ret = io_loop(NULL, NULL); + ok1(ret == &good); + + return exit_status(); +} diff --git a/ccan/ccan/io/test/run-50-listener-blocking-accept.c b/ccan/ccan/io/test/run-50-listener-blocking-accept.c new file mode 100644 index 000000000000..0fcd390be586 --- /dev/null +++ b/ccan/ccan/io/test/run-50-listener-blocking-accept.c @@ -0,0 +1,127 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Regression test: io_new_listener() must set the listen fd O_NONBLOCK + * (like io_new_conn() does for connections). Otherwise, if the pending + * connection vanishes between poll() and accept() (peer RSTs after the + * handshake, or a competing acceptor steals it), accept() blocks and + * hangs the entire io_loop. */ + +#define PORT "65150" + +static int listen_fd, client_fd; +static int rst_sent, done; + +static int rst_poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + int r; + nfds_t i; + + if (rst_sent) { + /* accept() has had its chance; stop here. Checked before + * poll(): nothing more will arrive on the fds, so polling + * first would block forever. */ + io_break(&done); + return 0; + } + r = poll(fds, nfds, timeout); + if (r > 0) { + for (i = 0; i < nfds; i++) { + if (fds[i].fd == listen_fd && (fds[i].revents & POLLIN)) { + /* Peer aborts the pending connection after + * poll() reported it, before accept(). */ + struct linger ling = { 1, 0 }; + setsockopt(client_fd, SOL_SOCKET, SO_LINGER, + &ling, sizeof(ling)); + close(client_fd); + rst_sent = 1; + } + } + } + return r; +} + +static struct io_plan *init_conn(struct io_conn *conn, void *unused) +{ + return io_close(conn); +} + +static int make_listen_fd(const char *port) +{ + int fd, on = 1; + struct addrinfo *addrinfo, hints; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + if (getaddrinfo(NULL, port, &hints, &addrinfo) != 0) + return -1; + fd = socket(addrinfo->ai_family, addrinfo->ai_socktype, + addrinfo->ai_protocol); + if (fd < 0) + return -1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (bind(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) { + close(fd); + return -1; + } + if (listen(fd, 1) != 0) { + close(fd); + return -1; + } + freeaddrinfo(addrinfo); + return fd; +} + +int main(void) +{ + struct sockaddr_in sa; + socklen_t salen = sizeof(sa); + int flags, status; + + plan_tests(4); + alarm(15); + + listen_fd = make_listen_fd(PORT); + ok1(listen_fd >= 0); + io_new_listener(NULL, listen_fd, init_conn, NULL); + + /* The fix: listener must be nonblocking. */ + flags = fcntl(listen_fd, F_GETFL); + ok1(flags != -1 && (flags & O_NONBLOCK)); + + /* Behavioral check in a child: RST the pending connection between + * poll() and accept(); a blocking accept() hangs (SIGALRM). */ + fflush(stdout); + if (fork() == 0) { + alarm(5); + client_fd = socket(AF_INET, SOCK_STREAM, 0); + if (getsockname(listen_fd, (struct sockaddr *)&sa, &salen) != 0) + exit(1); + if (connect(client_fd, (struct sockaddr *)&sa, salen) != 0) + exit(1); + io_poll_override(rst_poll); + io_loop(NULL, NULL); + exit(0); + } + + ok1(wait(&status) != -1); + ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0); + + return exit_status(); +} diff --git a/ccan/ccan/json_escape/json_escape.c b/ccan/ccan/json_escape/json_escape.c index 6344bd8a8985..ce748a12d881 100644 --- a/ccan/ccan/json_escape/json_escape.c +++ b/ccan/ccan/json_escape/json_escape.c @@ -61,6 +61,8 @@ static struct json_escape *escape(const tal_t *ctx, /* Worst case: all \uXXXX */ esc = (struct json_escape *)tal_arr(ctx, char, len * 6 + 1); + if (!esc) + return NULL; for (i = n = 0; i < len; i++, n++) { char escape = 0; diff --git a/ccan/ccan/json_out/json_out.c b/ccan/ccan/json_out/json_out.c index 915d525ef406..4b018bc96644 100644 --- a/ccan/ccan/json_out/json_out.c +++ b/ccan/ccan/json_out/json_out.c @@ -115,7 +115,8 @@ static void unindent(struct json_out *jout, char type) jout->empty = false; } -/* Make sure jout->outbuf has room for len: return pointer */ +/* Make sure jout->outbuf has room for len: return pointer, or NULL + * if the allocation failed. */ static char *mkroom(struct json_out *jout, size_t len) { ptrdiff_t delta = membuf_prepare_space(&jout->outbuf, len); @@ -123,6 +124,11 @@ static char *mkroom(struct json_out *jout, size_t len) if (delta && jout->move_cb) jout->move_cb(jout, delta, jout->cb_arg); + /* membuf_prepare_space() documents checking membuf_num_space() + * to detect allocation failure. */ + if (membuf_num_space(&jout->outbuf) < len) + return NULL; + return membuf_space(&jout->outbuf); } @@ -218,6 +224,7 @@ bool json_out_addv(struct json_out *jout, size_t fmtlen, avail; va_list ap2; char *dst; + int vsnprintf_ret; if (!json_out_member_direct(jout, fieldname, 0)) return false; @@ -236,7 +243,12 @@ bool json_out_addv(struct json_out *jout, /* Try printing in place first. */ dst = membuf_space(&jout->outbuf); - fmtlen = vsnprintf(dst + quote, avail, fmt, ap); + vsnprintf_ret = vsnprintf(dst + quote, avail, fmt, ap); + if (vsnprintf_ret < 0) { + dst = NULL; + goto out; + } + fmtlen = vsnprintf_ret; /* Horrible subtlety: vsnprintf *will* NUL terminate, even if it means * chopping off the last character. So if fmtlen == @@ -259,6 +271,10 @@ bool json_out_addv(struct json_out *jout, if (json_escape_needed(dst + quote, fmtlen)) { struct json_escape *e; e = json_escape_len(NULL, dst + quote, fmtlen); + if (!e) { + dst = NULL; + goto out; + } fmtlen = strlen(e->s); dst = mkroom(jout, fmtlen + (int)quote*2); if (!dst) @@ -307,6 +323,8 @@ bool json_out_addstrn(struct json_out *jout, if (json_escape_needed(str, len)) { e = json_escape_len(NULL, str, len); + if (!e) + return false; str = e->s; len = strlen(str); } else @@ -328,11 +346,15 @@ bool json_out_add_splice(struct json_out *jout, { const char *p; size_t len; + char *dst; p = json_out_contents(src, &len); if (!p) return false; - memcpy(json_out_member_direct(jout, fieldname, len), p, len); + dst = json_out_member_direct(jout, fieldname, len); + if (!dst) + return false; + memcpy(dst, p, len); return true; } diff --git a/ccan/ccan/json_out/test/run-oom-escape.c b/ccan/ccan/json_out/test/run-oom-escape.c new file mode 100644 index 000000000000..fbdef3e8c5e5 --- /dev/null +++ b/ccan/ccan/json_out/test/run-oom-escape.c @@ -0,0 +1,72 @@ +/* Regression test (auditor-added, temporary): the escape paths do not + * handle json_escape_len() returning NULL (it is a tal allocation and + * fails on OOM): + * json_out_addv (ccan/json_out/json_out.c:261-262): strlen(e->s) + * json_out_addstrn (ccan/json_out/json_out.c:309-311): str = e->s + * + * Today the crash actually happens one frame earlier, inside the + * dependency: ccan/json_escape/json_escape.c escape() does not check + * its tal_arr() result either (json_escape.c:63, first dereferenced at + * json_escape.c:123). This test must pass after BOTH are repaired: + * json_escape returning NULL on OOM and json_out mapping that to a + * false return. + * + * Allocation failure is simulated with tal_set_backend installed only + * around the call under test, so the escape allocation is the one that + * fails. + * + * Currently fails (NULL dereference); must pass after repair. + */ +#include "config.h" + +#include +#include + +#include +#include +#include + +static void ignoring_error(const char *msg) +{ + (void)msg; +} + +static void *failing_alloc(size_t size) +{ + (void)size; + return NULL; +} + +static void restore_backend(void) +{ + tal_set_backend(malloc, realloc, free, (void (*)(const char *))abort); +} + +int main(void) +{ + struct json_out *jout; + bool ok; + + alarm(10); + plan_tests(2); + + /* json_out_add() with a string needing escape. */ + jout = json_out_new(NULL); + json_out_start(jout, NULL, '{'); + tal_set_backend(failing_alloc, NULL, NULL, ignoring_error); + ok = json_out_add(jout, "f", true, "%s", "a\nb"); + restore_backend(); + ok1(!ok); + tal_free(jout); + + /* json_out_addstrn() with a string needing escape. */ + jout = json_out_new(NULL); + json_out_start(jout, NULL, '{'); + tal_set_backend(failing_alloc, NULL, NULL, ignoring_error); + ok = json_out_addstrn(jout, "f", "a\nb", 3); + restore_backend(); + ok1(!ok); + tal_free(jout); + + return exit_status(); +} diff --git a/ccan/ccan/json_out/test/run-oom.c b/ccan/ccan/json_out/test/run-oom.c new file mode 100644 index 000000000000..d2b7c56ba9ee --- /dev/null +++ b/ccan/ccan/json_out/test/run-oom.c @@ -0,0 +1,108 @@ +/* Regression test (auditor-added, temporary): json_out's documented OOM + * contract is graceful failure: + * json_out_add: "Returns true unless tal_resize() fails." + * json_out_member_direct: "Returns ... or NULL if tal_resize() fails." + * json_out_direct: "Returns ... or NULL if tal_resize() fails." + * json_out_add_splice: "Returns false if tal_resize() fails." + * + * But mkroom() (ccan/json_out/json_out.c:119-127) never checks whether + * membuf_prepare_space() actually made room (membuf.h documents the + * failure check: membuf_num_space() < num_extra), so on tal_resize + * failure it returns a pointer into the too-small old buffer and + * callers write past the end (heap buffer overflow) or hit the + * membuf_added() assertion. json_out_add_splice additionally ignores + * json_out_member_direct()'s return (json_out.c:335): once mkroom() is + * fixed to return NULL, that is memcpy(NULL, p, len). + * + * Allocation failure is simulated with tal_set_backend (resize_fn + * always fails, error_fn returns instead of aborting). + * + * Currently fails (heap overflow / assertion abort); must pass after + * repair. + */ +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +static void ignoring_error(const char *msg) +{ + (void)msg; +} + +static void *failing_resize(void *p, size_t size) +{ + (void)p; + (void)size; + return NULL; +} + +static void restore_backend(void) +{ + tal_set_backend(malloc, realloc, free, (void (*)(const char *))abort); +} + +int main(void) +{ + struct json_out *jout, *src; + char big[1000]; + char *p; + bool ok; + + alarm(10); + plan_tests(4); + + memset(big, 'x', sizeof(big)); + big[sizeof(big) - 1] = '\0'; + + /* json_out_add() must return false when tal_resize() fails. */ + jout = json_out_new(NULL); + json_out_start(jout, NULL, '{'); + tal_set_backend(NULL, failing_resize, NULL, ignoring_error); + ok = json_out_add(jout, "f", true, "%s", big); + restore_backend(); + ok1(!ok); + tal_free(jout); + + /* json_out_member_direct() must return NULL. */ + jout = json_out_new(NULL); + json_out_start(jout, NULL, '{'); + tal_set_backend(NULL, failing_resize, NULL, ignoring_error); + p = json_out_member_direct(jout, "f", 1000); + restore_backend(); + ok1(p == NULL); + tal_free(jout); + + /* json_out_direct() must return NULL. */ + jout = json_out_new(NULL); + json_out_start(jout, NULL, '{'); + tal_set_backend(NULL, failing_resize, NULL, ignoring_error); + p = json_out_direct(jout, 1000); + restore_backend(); + ok1(p == NULL); + tal_free(jout); + + /* json_out_add_splice() must return false. */ + src = json_out_new(NULL); + json_out_start(src, NULL, '{'); + json_out_addstr(src, "x", "hello"); + json_out_end(src, '}'); + json_out_finished(src); + + jout = json_out_new(NULL); + json_out_start(jout, NULL, '{'); + memset(json_out_direct(jout, 50), ' ', 50); + tal_set_backend(NULL, failing_resize, NULL, ignoring_error); + ok = json_out_add_splice(jout, "inner", src); + restore_backend(); + ok1(!ok); + tal_free(jout); + tal_free(src); + + return exit_status(); +} diff --git a/ccan/ccan/json_out/test/run-vsnprintf-error.c b/ccan/ccan/json_out/test/run-vsnprintf-error.c new file mode 100644 index 000000000000..0b5b3657e196 --- /dev/null +++ b/ccan/ccan/json_out/test/run-vsnprintf-error.c @@ -0,0 +1,53 @@ +/* Regression test (auditor-added, temporary): json_out_addv() stores the + * vsnprintf() return (an int, -1 on error) in a size_t + * (ccan/json_out/json_out.c:218,239), so a failing conversion yields + * fmtlen == SIZE_MAX. With quote=true that reaches + * json_escape_len(NULL, dst, SIZE_MAX) whose len*6+1 sizing overflows + * and aborts in tal ("allocation size overflow"); with quote=false it + * reaches membuf_added(outbuf, SIZE_MAX), corrupting the buffer (or + * hitting its assertion). + * + * glibc vsnprintf returns -1 (EILSEQ) for "%lc" with an invalid wchar; + * the probe skips the test on platforms where it does not. + * + * Currently fails (abort); must pass (return false) after repair. + */ +#include "config.h" + +#include +#include +#include + +#include +#include + +int main(void) +{ + struct json_out *jout; + char probe[8]; + int r; + + alarm(10); + plan_tests(2); + + r = snprintf(probe, sizeof(probe), "%lc", (wint_t)0xD800); + if (r >= 0) { + ok1(true); /* SKIP: platform vsnprintf does not fail here */ + ok1(true); /* SKIP */ + return exit_status(); + } + + /* quote=true path. */ + jout = json_out_new(NULL); + json_out_start(jout, NULL, '{'); + ok1(!json_out_add(jout, "f", true, "%lc", (wint_t)0xD800)); + tal_free(jout); + + /* quote=false path. */ + jout = json_out_new(NULL); + json_out_start(jout, NULL, '{'); + ok1(!json_out_add(jout, "f", false, "%lc", (wint_t)0xD800)); + tal_free(jout); + + return exit_status(); +} diff --git a/ccan/ccan/likely/likely.c b/ccan/ccan/likely/likely.c index aabb51ed2471..d015a37931a8 100644 --- a/ccan/ccan/likely/likely.c +++ b/ccan/ccan/likely/likely.c @@ -99,7 +99,7 @@ char *likely_stats(unsigned int min_hits, unsigned int percent) } } - if (worst_ratio * 100 > percent) + if (!worst || worst_ratio * 100 > percent) return NULL; maxlen = strlen(worst->condstr) + diff --git a/ccan/ccan/likely/test/run-stats-empty-debug.c b/ccan/ccan/likely/test/run-stats-empty-debug.c new file mode 100644 index 000000000000..36aab1ed373a --- /dev/null +++ b/ccan/ccan/likely/test/run-stats-empty-debug.c @@ -0,0 +1,44 @@ +#define CCAN_LIKELY_DEBUG 1 +#include +#include +#include +#include +#include + +/* Regression test for likely.c likely_stats(): with no entry meeting + * the criteria, the worst_ratio guard is skipped when percent >= 200 + * (worst_ratio stays 2.0; 2.0 * 100 > percent is false), and worst + * (NULL) is dereferenced at strlen(worst->condstr). The header + * documents: "It returns NULL when nothing meets those criteria." */ +static bool one_seems_likely(unsigned int val) +{ + if (likely(val == 1)) + return true; + return false; +} + +int main(void) +{ + char *bad; + + alarm(10); + plan_tests(3); + + /* Empty table: nothing meets the criteria, must return NULL. */ + bad = likely_stats(0, 200); + ok1(bad == NULL); + + /* Entry below min_hits: likewise nothing meets the criteria. */ + one_seems_likely(0); + one_seems_likely(2); + bad = likely_stats(4, 200); + ok1(bad == NULL); + + /* A qualifying entry must still be reported with percent=200. */ + bad = likely_stats(2, 200); + ok(bad != NULL && strends(bad, "correct 0% (0/2)"), + "likely_stats returned %s", bad ? bad : "(null)"); + free(bad); + + exit(exit_status()); +} diff --git a/ccan/ccan/mem/mem.c b/ccan/ccan/mem/mem.c index 13027a2a7b0f..ddb84c9d108a 100644 --- a/ccan/ccan/mem/mem.c +++ b/ccan/ccan/mem/mem.c @@ -33,7 +33,7 @@ void *memrchr(const void *s, int c, size_t n) unsigned char *p = (unsigned char *)s; while (n) { - if (p[n-1] == c) + if (p[n-1] == (unsigned char)c) return p + n - 1; n--; } @@ -56,11 +56,11 @@ void *mempbrkm(const void *data_, size_t len, const void *accept_, size_t accept void *memcchr(void const *data, int c, size_t data_len) { - char const *p = data; + unsigned char const *p = data; size_t i; for (i = 0; i < data_len; i++) - if (p[i] != c) + if (p[i] != (unsigned char)c) return (void *)&p[i]; return NULL; diff --git a/ccan/ccan/mem/mem.h b/ccan/ccan/mem/mem.h index 20286dcbefd4..2c61b92d6fc5 100644 --- a/ccan/ccan/mem/mem.h +++ b/ccan/ccan/mem/mem.h @@ -219,6 +219,9 @@ static inline bool memends_str(const void *a, size_t al, const char *s) * @al: length of first memory range * @b: pointer to second memory range * @al: length of second memory range + * + * Note that a zero-length range counts as overlapping any range that + * straddles its address. */ CONST_FUNCTION static inline bool memoverlaps(const void *a_, size_t al, diff --git a/ccan/ccan/mem/test/api.c b/ccan/ccan/mem/test/api.c index 59b25947ab0a..f7dc28d9e884 100644 --- a/ccan/ccan/mem/test/api.c +++ b/ccan/ccan/mem/test/api.c @@ -1,6 +1,7 @@ #include "config.h" #include +#include #include #include @@ -96,8 +97,11 @@ int main(void) haystack1 + sizeof(haystack1), 1)); ok1(!memoverlaps(haystack1 + sizeof(haystack1), 1, haystack1, sizeof(haystack1))); - ok1(!memoverlaps(haystack1, sizeof(haystack1), haystack1 - 1, 1)); - ok1(!memoverlaps(haystack1 - 1, 1, haystack1, sizeof(haystack1))); + /* Forming haystack1 - 1 directly is UB; round-trip via uintptr_t. */ + ok1(!memoverlaps(haystack1, sizeof(haystack1), + (void *)((uintptr_t)haystack1 - 1), 1)); + ok1(!memoverlaps((void *)((uintptr_t)haystack1 - 1), 1, + haystack1, sizeof(haystack1))); ok1(memoverlaps(haystack1, 5, haystack1 + 4, 7)); ok1(!memoverlaps(haystack1, 5, haystack1 + 5, 6)); ok1(memoverlaps(haystack1 + 4, 7, haystack1, 5)); diff --git a/ccan/ccan/mem/test/run-memcchr-range.c b/ccan/ccan/mem/test/run-memcchr-range.c new file mode 100644 index 000000000000..e6d470420469 --- /dev/null +++ b/ccan/ccan/mem/test/run-memcchr-range.c @@ -0,0 +1,45 @@ +/* Regression test (auditor-added, temporary): memcchr must treat c the + * way memchr does -- converted to unsigned char -- since it is + * documented as "The complement of memchr()" (mem.h:76). + * + * ccan/mem/mem.c:63 compares a *signed* char against int c, so bytes + * 0x80..0xFF are never considered equal to c values 128..255, and + * c values > 255 never match the byte they convert to. + * + * Currently fails 3/3; must pass after repair. + */ +#include "config.h" + +#include +#include + +#include +#include + +int main(void) +{ + char buf80[] = { (char)0x80, (char)0x80, 'x' }; + char bufff[] = { (char)0xFF, 'y' }; + char buf00[] = { 0x00, 'z' }; + + alarm(10); + plan_tests(6); + + /* Sanity: memchr itself converts c to unsigned char. */ + ok1(memchr(buf80, 0x80, sizeof(buf80)) == buf80); + ok1(memchr(buf00, 0x100, sizeof(buf00)) == buf00); + + /* c = 0x80: the complement must skip the two 0x80 bytes. */ + ok1(memcchr(buf80, 0x80, sizeof(buf80)) == buf80 + 2); + + /* c = 255: the complement must skip the 0xFF byte. */ + ok1(memcchr(bufff, 255, sizeof(bufff)) == bufff + 1); + + /* c = 0x100 converts to 0x00: the complement must skip the NUL. */ + ok1(memcchr(buf00, 0x100, sizeof(buf00)) == buf00 + 1); + + /* An ordinary in-range c must still work. */ + ok1(memcchr(buf80, 'x', sizeof(buf80)) == buf80); + + return exit_status(); +} diff --git a/ccan/ccan/mem/test/run-memrchr-fallback.c b/ccan/ccan/mem/test/run-memrchr-fallback.c new file mode 100644 index 000000000000..47b44610d457 --- /dev/null +++ b/ccan/ccan/mem/test/run-memrchr-fallback.c @@ -0,0 +1,46 @@ +/* Regression test (auditor-added, temporary): the memrchr fallback in + * ccan/mem/mem.c:30-43 (compiled when HAVE_MEMRCHR == 0) must convert + * c to unsigned char, as memchr does (C17 7.24.5.1/2; memrchr is + * "like memchr, searching backward"). mem.c:36 compares + * p[n-1] == c without the conversion, so any c outside 0..255 + * (e.g. a negative char, or an int with high bits set) never matches, + * where the real memrchr finds the byte. + * + * To exercise the fallback regardless of host libc, this test forces + * HAVE_MEMRCHR to 0 and includes the module source directly. + * + * Currently fails 3/5; must pass after repair. + */ +#include "config.h" + +/* Force the fallback implementation, whatever the host provides. */ +#undef HAVE_MEMRCHR +#define HAVE_MEMRCHR 0 + +#include + +#include +#include + +int main(void) +{ + const unsigned char buf[] = { 0x41, 0xFF, 0x42 }; + + alarm(10); + plan_tests(5); + + /* Baseline: ordinary c values work. */ + ok1(memrchr(buf, 0x42, sizeof(buf)) == buf + 2); + ok1(memrchr(buf, 'q', sizeof(buf)) == NULL); + + /* c = -1 must behave as (unsigned char)-1 == 0xFF. */ + ok1(memrchr(buf, -1, sizeof(buf)) == buf + 1); + + /* c = 0x1FF must behave as 0xFF. */ + ok1(memrchr(buf, 0x1FF, sizeof(buf)) == buf + 1); + + /* c = 0x141 must behave as 0x41 ('A'). */ + ok1(memrchr(buf, 0x141, sizeof(buf)) == buf + 0); + + return exit_status(); +} diff --git a/ccan/ccan/membuf/membuf.c b/ccan/ccan/membuf/membuf.c index 39841d9705a5..6d49b74894be 100644 --- a/ccan/ccan/membuf/membuf.c +++ b/ccan/ccan/membuf/membuf.c @@ -3,6 +3,7 @@ #include #include #include +#include void membuf_init_(struct membuf *mb, void *elems, size_t num_elems, size_t elemsize, @@ -42,6 +43,12 @@ size_t membuf_prepare_space_(struct membuf *mb, if (num_extra < mb->max_elems) num_extra = mb->max_elems; + /* Don't let the allocation size wrap. */ + if (num_extra > SIZE_MAX / elemsize - mb->max_elems) { + errno = ENOMEM; + return 0; + } + expand = mb->expandfn(mb, mb->elems, (mb->max_elems + num_extra) * elemsize); if (!expand) { @@ -51,6 +58,9 @@ size_t membuf_prepare_space_(struct membuf *mb, mb->elems = expand; } } + /* Nothing moved if there was no old buffer. */ + if (!oldstart) + return 0; return (char *)membuf_elems_(mb, elemsize) - oldstart; } diff --git a/ccan/ccan/membuf/membuf.h b/ccan/ccan/membuf/membuf.h index aebfca27d4f1..52a7bd02f186 100644 --- a/ccan/ccan/membuf/membuf.h +++ b/ccan/ccan/membuf/membuf.h @@ -83,6 +83,8 @@ static inline size_t membuf_num_elems_(const struct membuf *mb) static inline void *membuf_elems_(const struct membuf *mb, size_t elemsize) { + if (!mb->elems) + return NULL; return mb->elems + mb->start * elemsize; } @@ -130,6 +132,8 @@ static inline size_t membuf_num_space_(const struct membuf *mb) static inline void *membuf_space_(struct membuf *mb, size_t elemsize) { + if (!mb->elems) + return NULL; return mb->elems + mb->end * elemsize; } diff --git a/ccan/ccan/membuf/test/run-null-init.c b/ccan/ccan/membuf/test/run-null-init.c new file mode 100644 index 000000000000..be816bcb2026 --- /dev/null +++ b/ccan/ccan/membuf/test/run-null-init.c @@ -0,0 +1,48 @@ +/* Temporary auditor regression test (kimi-code audit, 2026-08-05). + * Exercises the documented empty-NULL initialization idiom + * (membuf.h:48: membuf_init(&intp_membuf, NULL, 0, membuf_realloc)). + * Passes in plain builds; aborts under UBSan today because + * membuf_elems_/membuf_space_ compute NULL + 0 (membuf.h:86,133) and + * membuf_prepare_space_ subtracts NULL (membuf.c:54). + * After repair it must run UBSan-clean. + */ +#include +#include +#include +#include + +/* Include the C file directly. */ +#include +#include + +int main(void) +{ + MEMBUF(int) mb; + int *p; + size_t delta; + + plan_tests(7); + alarm(10); + + membuf_init(&mb, NULL, 0, membuf_realloc); + ok1(membuf_num_elems(&mb) == 0); + ok1(membuf_num_space(&mb) == 0); + ok1(membuf_elems(&mb) == NULL); + + /* Grow from NULL; fill, consume, verify. */ + delta = membuf_prepare_space(&mb, 4); + ok1(membuf_num_space(&mb) >= 4); + (void)delta; + p = membuf_space(&mb); + for (int i = 0; i < 4; i++) + p[i] = i + 1; + membuf_added(&mb, 4); + ok1(membuf_num_elems(&mb) == 4); + ok1(memcmp(membuf_elems(&mb), (int[]){1, 2, 3, 4}, + 4 * sizeof(int)) == 0); + membuf_consume(&mb, 4); + ok1(membuf_num_elems(&mb) == 0); + + free(membuf_cleanup(&mb)); + return exit_status(); +} diff --git a/ccan/ccan/membuf/test/run-overflow.c b/ccan/ccan/membuf/test/run-overflow.c new file mode 100644 index 000000000000..1df475d9ec62 --- /dev/null +++ b/ccan/ccan/membuf/test/run-overflow.c @@ -0,0 +1,68 @@ +/* Temporary auditor regression test (kimi-code audit, 2026-08-05). + * Proves: membuf_prepare_space_() growth arithmetic + * (mb->max_elems + num_extra) * elemsize (ccan/membuf/membuf.c:45-46) + * overflows size_t, so expandfn is called with a tiny wrapped size, + * "succeeds", and the documented failure check (membuf_num_space() < + * num_extra, membuf.h:163-165) reports SUCCESS with a far-too-small + * buffer. Fails against current code; must pass after repair. + * The test is memory-safe even when failing: it never writes into the + * bogus buffer. + */ +#include +#include +#include +#include +#include + +/* Include the C file directly. */ +#include +#include + +static void *fail_expand(struct membuf *mb, void *elems, size_t newsize) +{ + (void)mb; (void)elems; (void)newsize; + return NULL; +} + +int main(void) +{ + MEMBUF(int) mb; + /* (4 + num_extra) * sizeof(int) wraps to 16 on LP64 and ILP32. */ + size_t num_extra = (SIZE_MAX / sizeof(int)) + 1; + + plan_tests(6); + alarm(10); + + /* Control 1: ordinary growth still works and is reported. */ + membuf_init(&mb, malloc(4 * sizeof(int)), 4, membuf_realloc); + membuf_prepare_space(&mb, 8); + ok1(membuf_num_space(&mb) >= 8); + free(membuf_cleanup(&mb)); + + /* Control 2: genuine expandfn failure is reported as documented. */ + membuf_init(&mb, malloc(4 * sizeof(int)), 4, fail_expand); + errno = 0; + membuf_prepare_space(&mb, 8); + ok1(membuf_num_space(&mb) < 8); + ok1(errno == ENOMEM); + free(membuf_cleanup(&mb)); + + /* The defect: an impossible growth request must FAIL (ENOMEM, + * num_space < num_extra), not "succeed" with a wrapped-size + * allocation. */ + membuf_init(&mb, malloc(4 * sizeof(int)), 4, membuf_realloc); + errno = 0; + membuf_prepare_space(&mb, num_extra); + ok1(membuf_num_space(&mb) < num_extra); + ok1(errno == ENOMEM); + free(membuf_cleanup(&mb)); + + /* Same, from the documented empty-NULL initial state. */ + membuf_init(&mb, NULL, 0, membuf_realloc); + errno = 0; + membuf_prepare_space(&mb, num_extra); + ok1(membuf_num_space(&mb) < num_extra); + free(membuf_cleanup(&mb)); + + return exit_status(); +} diff --git a/ccan/ccan/opt/helpers.c b/ccan/ccan/opt/helpers.c index 5db87bba3b04..5a71be26c569 100644 --- a/ccan/ccan/opt/helpers.c +++ b/ccan/ccan/opt/helpers.c @@ -60,8 +60,7 @@ char *opt_set_charp(const char *arg, char **p) return NULL; } -/* Set an integer value, various forms. - FIXME: set to 1 on arg == NULL ? */ +/* Set an integer value, various forms. */ char *opt_set_intval(const char *arg, int *i) { long l; diff --git a/ccan/ccan/opt/opt.h b/ccan/ccan/opt/opt.h index d6f5634e9b50..60204acae923 100644 --- a/ccan/ccan/opt/opt.h +++ b/ccan/ccan/opt/opt.h @@ -447,14 +447,14 @@ char *opt_set_charp(const char *arg, char **p); /* If *p is NULL, this returns false (i.e. doesn't show a default) */ bool opt_show_charp(char *buf, size_t len, char *const *p); -/* Set an integer value, various forms. Sets to 1 on arg == NULL. */ -char *opt_set_intval(const char *arg, int *i); +/* Set an integer value, various forms. */ +char *opt_set_intval(const char *arg, int *i) NO_NULL_ARGS; bool opt_show_intval(char *buf, size_t len, const int *i); -char *opt_set_uintval(const char *arg, unsigned int *ui); +char *opt_set_uintval(const char *arg, unsigned int *ui) NO_NULL_ARGS; bool opt_show_uintval(char *buf, size_t len, const unsigned int *ui); -char *opt_set_longval(const char *arg, long *l); +char *opt_set_longval(const char *arg, long *l) NO_NULL_ARGS; bool opt_show_longval(char *buf, size_t len, const long *l); -char *opt_set_ulongval(const char *arg, unsigned long *ul); +char *opt_set_ulongval(const char *arg, unsigned long *ul) NO_NULL_ARGS; bool opt_show_ulongval(char *buf, size_t len, const unsigned long *ul); /* Set an floating point value, various forms. */ diff --git a/ccan/ccan/opt/parse.c b/ccan/ccan/opt/parse.c index b932bf333571..7bf31cb3d97d 100644 --- a/ccan/ccan/opt/parse.c +++ b/ccan/ccan/opt/parse.c @@ -92,12 +92,13 @@ int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset, arg = 1; } else { for (arg = 1; argv[arg]; arg++) { - if (argv[arg][0] == '-') + if (argv[arg][0] == '-' && argv[arg][1]) break; } } - if (!argv[arg] || argv[arg][0] != '-') + /* A bare '-' is an operand, not an option. */ + if (!argv[arg] || argv[arg][0] != '-' || argv[arg][1] == '\0') return 0; /* Special arg terminator option. */ diff --git a/ccan/ccan/opt/test/run-early-incomplete-dash.c b/ccan/ccan/opt/test/run-early-incomplete-dash.c new file mode 100644 index 000000000000..9fcaa5043f88 --- /dev/null +++ b/ccan/ccan/opt/test/run-early-incomplete-dash.c @@ -0,0 +1,57 @@ +/* Regression test for the bare "-" argument in opt_early_parse_incomplete(). + * + * parse_one() looks up argv[arg][*offset + 1] as a short option. For a + * bare "-" that is the terminating NUL (which can never be a registered + * option); the unknown_ok path then increments *offset past the NUL and + * the "any more letters?" check reads argv[arg][*offset + 1], one byte + * beyond the end of the string. With execve-style packed argv strings + * that byte is the first character of the NEXT argument, so a non-option + * operand can be mistaken for short-option letters inside "-", firing + * callbacks for arguments the user never supplied as options. + * + * Currently fails (test 2) without the fix. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +static bool v; + +int main(void) +{ + alarm(10); + plan_tests(4); + + opt_register_early_noarg("-v", opt_set_bool, &v, "verbose"); + + /* Mimic execve string packing: "prog\0-\0v\0"; the byte after the + * "-" string is 'v', the first char of the next (non-option) arg. */ + { + static char storage[] = "prog\0-\0v\0"; + char *myargv[4] = { storage, storage + 5, storage + 7, NULL }; + + v = false; + ok1(opt_early_parse_incomplete(3, myargv, save_err_output)); + ok1(v == false); /* "v" is an operand; callback must not fire */ + } + + /* An isolated bare "-" must be handled (and terminate) too. */ + { + char *myargv[3] = { (char *)"prog", (char *)"-", NULL }; + + v = false; + ok1(opt_early_parse_incomplete(2, myargv, save_err_output)); + ok1(v == false); + } + + opt_free_table(); + free(err_output); + return exit_status(); +} diff --git a/ccan/ccan/order/test/run-fancy.c b/ccan/ccan/order/test/run-fancy.c index 5a287c4cbcfc..fbd6ac72de0f 100644 --- a/ccan/ccan/order/test/run-fancy.c +++ b/ccan/ccan/order/test/run-fancy.c @@ -56,13 +56,13 @@ int main(void) ok1(total_order_cmp(order2, &item2, &item2) == 0); ok1(total_order_cmp(order2, &item3, &item3) == 0); - ok1(total_order_cmp(order2, &item1, &item2) == 1); + ok1(total_order_cmp(order2, &item1, &item2) == -1); ok1(total_order_cmp(order2, &item2, &item3) == 1); ok1(total_order_cmp(order2, &item1, &item3) == 1); - ok1(total_order_cmp(order2, &item2, &item1) == -1); + ok1(total_order_cmp(order2, &item2, &item1) == 1); ok1(total_order_cmp(order2, &item3, &item2) == -1); ok1(total_order_cmp(order2, &item3, &item1) == -1); - - exit(0); + + return exit_status(); } diff --git a/ccan/ccan/pipecmd/pipecmd.c b/ccan/ccan/pipecmd/pipecmd.c index 0090275b0155..80447707e3f6 100644 --- a/ccan/ccan/pipecmd/pipecmd.c +++ b/ccan/ccan/pipecmd/pipecmd.c @@ -54,6 +54,7 @@ pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, int child_close[4], num_child_close = 0; pid_t childpid; int err; + ssize_t r; if (fd_tochild) { if (fd_tochild == &pipecmd_preserve) { @@ -164,7 +165,9 @@ pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, if (write(execfail[1], &err, sizeof(err))) { ; } - exit(127); + /* _exit: don't flush the parent's stdio buffers again, + * nor run its atexit handlers. */ + _exit(127); } int i; @@ -172,9 +175,22 @@ pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, close(par_close[i]); /* Child will close this without writing on successful exec. */ - if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) { + do { + r = read(execfail[0], &err, sizeof(err)); + } while (r < 0 && errno == EINTR); + + if (r == sizeof(err)) { close(execfail[0]); - waitpid(childpid, NULL, 0); + /* Useless now: close the parent-side pipe ends too. */ + if (fd_tochild && fd_tochild != &pipecmd_preserve) + close(tochild[1]); + if (fd_fromchild && fd_fromchild != &pipecmd_preserve) + close(fromchild[0]); + if (fd_errfromchild && fd_errfromchild != &pipecmd_preserve + && fd_errfromchild != fd_fromchild) + close(errfromchild[0]); + while (waitpid(childpid, NULL, 0) < 0 && errno == EINTR) + ; errno = err; return -1; } @@ -190,6 +206,8 @@ pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild, fail: for (i = 0; i < num_par_close; i++) close_noerr(par_close[i]); + for (i = 0; i < num_child_close; i++) + close_noerr(child_close[i]); return -1; } diff --git a/ccan/ccan/pipecmd/test/run-eintr.c b/ccan/ccan/pipecmd/test/run-eintr.c new file mode 100644 index 000000000000..3b9efcf4e46f --- /dev/null +++ b/ccan/ccan/pipecmd/test/run-eintr.c @@ -0,0 +1,56 @@ +/* Regression test: a signal whose handler was installed without + * SA_RESTART interrupts the parent's blocking read(execfail[0]); + * pipecmdarr must not then report success for a command which does + * not exist. */ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include +#include +#include + +static void handler(int sig) +{ + (void)sig; +} + +int main(void) +{ + struct sigaction sa; + struct itimerval it, disarm; + int i, false_success = 0; + + alarm(60); + plan_tests(1); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sa.sa_flags = 0; /* no SA_RESTART */ + sigaction(SIGALRM, &sa, NULL); + + memset(&it, 0, sizeof(it)); + it.it_interval.tv_usec = 50; + it.it_value.tv_usec = 50; + memset(&disarm, 0, sizeof(disarm)); + + for (i = 0; i < 2000; i++) { + pid_t child; + int status; + + setitimer(ITIMER_REAL, &it, NULL); + child = pipecmd(NULL, NULL, NULL, "/doesnotexist", NULL); + setitimer(ITIMER_REAL, &disarm, NULL); + if (child != -1) { + /* Reported success: did the command actually run? */ + if (waitpid(child, &status, 0) == child + && WIFEXITED(status) && WEXITSTATUS(status) == 127) + false_success++; + } + } + ok1(false_success == 0); + + return exit_status(); +} diff --git a/ccan/ccan/pipecmd/test/run-execfail-fdleak.c b/ccan/ccan/pipecmd/test/run-execfail-fdleak.c new file mode 100644 index 000000000000..7c2900024456 --- /dev/null +++ b/ccan/ccan/pipecmd/test/run-execfail-fdleak.c @@ -0,0 +1,88 @@ +/* Regression test: pipecmdarr leaks the parent-side pipe fds when + * (a) the exec fails (child reports errno via the execfail pipe), or + * (b) pipe()/fcntl()/fork() fails after the command pipes were created. + * Both paths only close par_close[] and forget tochild[1], fromchild[0] + * and errfromchild[0]. */ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include +#include +#include + +static int count_fds(void) +{ + DIR *d = opendir("/proc/self/fd"); + struct dirent *de; + int n = 0; + + if (!d) + return -1; + while ((de = readdir(d)) != NULL) { + if (de->d_name[0] != '.') + n++; + } + closedir(d); + return n; +} + + +#if defined(__has_include) +#if __has_include() +#include +#define HAVE_VALGRIND_H 1 +#endif +#endif + +int main(void) +{ +#ifdef HAVE_VALGRIND_H + /* valgrind perturbs the child-exit/fd-count behavior this test + * measures. */ + if (RUNNING_ON_VALGRIND) { + plan_skip_all("not meaningful under valgrind"); + return exit_status(); + } +#endif + pid_t child; + int infd, outfd, errfd, before, after, saved_errno; + struct rlimit rl, saved; + char *const arr[] = { (char *)"/bin/true", NULL }; + + alarm(10); + plan_tests(5); + + /* (a) exec failure with all pipes requested must not leak fds. */ + before = count_fds(); + errno = 0; + child = pipecmd(&infd, &outfd, &errfd, "/doesnotexist", NULL); + saved_errno = errno; + ok1(child == -1); + ok1(saved_errno == ENOENT); + after = count_fds(); + ok1(after == before); + + /* (b) pipe(execfail) failing after the three command pipes were + * created must not leak the parent-side ends either. */ + getrlimit(RLIMIT_NOFILE, &saved); + before = count_fds(); + rl = saved; + /* Room for exactly the three command pipes (6 fds). */ + rl.rlim_cur = before + 6; + if (setrlimit(RLIMIT_NOFILE, &rl) != 0) { + ok(0, "setrlimit failed"); + exit(1); + } + errno = 0; + child = pipecmdarr(&infd, &outfd, &errfd, arr); + saved_errno = errno; + setrlimit(RLIMIT_NOFILE, &saved); + ok1(child == -1 && saved_errno == EMFILE); + after = count_fds(); + ok1(after == before); + + return exit_status(); +} diff --git a/ccan/ccan/pipecmd/test/run-execfail-flush.c b/ccan/ccan/pipecmd/test/run-execfail-flush.c new file mode 100644 index 000000000000..09675a2623b0 --- /dev/null +++ b/ccan/ccan/pipecmd/test/run-execfail-flush.c @@ -0,0 +1,75 @@ +/* Regression test: on exec failure the child calls exit(127) instead of + * _exit(127), so it flushes the parent's inherited stdio buffers a second + * time (and runs atexit handlers) when an output stream is preserved. */ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include +#include + + +#if defined(__has_include) +#if __has_include() +#include +#define HAVE_VALGRIND_H 1 +#endif +#endif + +int main(void) +{ +#ifdef HAVE_VALGRIND_H + /* valgrind perturbs the child-exit/fd-count behavior this test + * measures. */ + if (RUNNING_ON_VALGRIND) { + plan_skip_all("not meaningful under valgrind"); + return exit_status(); + } +#endif + char template[] = "/tmp/run-execfail-flush.XXXXXX"; + int fd, oldfd, saved_errno; + pid_t child; + FILE *f; + char buf[64]; + size_t n; + + alarm(10); + plan_tests(4); + + fd = mkstemp(template); + ok1(fd >= 0); + + /* Mug stdout with a file: fully buffered, so the printf below + * stays in the stdio buffer across fork(). */ + oldfd = dup(STDOUT_FILENO); + if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) + exit(1); + close(fd); + printf("unflushed-data"); /* no newline: stays buffered */ + + errno = 0; + child = pipecmd(NULL, &pipecmd_preserve, NULL, "/doesnotexist", NULL); + saved_errno = errno; + + /* Parent's own flush, then restore stdout for TAP output. */ + fflush(stdout); + if (dup2(oldfd, STDOUT_FILENO) != STDOUT_FILENO) + exit(1); + close(oldfd); + + ok1(child == -1); + ok1(saved_errno == ENOENT); + + f = fopen(template, "r"); + n = fread(buf, 1, sizeof(buf) - 1, f); + buf[n] = '\0'; + fclose(f); + unlink(template); + + /* The child must not have flushed the inherited buffer. */ + ok1(strcmp(buf, "unflushed-data") == 0); + + return exit_status(); +} diff --git a/ccan/ccan/ptr_valid/ptr_valid.c b/ccan/ccan/ptr_valid/ptr_valid.c index 7931984023ce..ff7ee040fb16 100644 --- a/ccan/ccan/ptr_valid/ptr_valid.c +++ b/ccan/ccan/ptr_valid/ptr_valid.c @@ -28,9 +28,10 @@ static char *grab(const char *filename) while ((ret = read(fd, buffer + s, max - s)) > 0) { s += ret; if (s == max) { - buffer = realloc(buffer, max*2+1); - if (!buffer) - goto close; + char *nb = realloc(buffer, max*2+1); + if (!nb) + goto free; + buffer = nb; max *= 2; } } @@ -62,10 +63,14 @@ static struct ptr_valid_map *add_map(struct ptr_valid_map *map, unsigned long start, unsigned long end, bool is_write) { if (*num == *max) { + struct ptr_valid_map *newmap; *max *= 2; - map = realloc(map, sizeof(*map) * *max); - if (!map) + newmap = realloc(map, sizeof(*newmap) * *max); + if (!newmap) { + free(map); return NULL; + } + map = newmap; } map[*num].start = (void *)start; map[*num].end = (void *)end; @@ -182,9 +187,9 @@ static void run_child(int infd, int outfd) /* This is weird. */ if (read(infd, &size, sizeof(size)) != sizeof(size)) - exit(1); + _exit(1); if (read(infd, &is_write, sizeof(is_write)) != sizeof(is_write)) - exit(2); + _exit(2); for (i = 0; i < size; i++) { ret = p[i]; @@ -194,9 +199,9 @@ static void run_child(int infd, int outfd) /* If we're still here, the answer is "yes". */ if (write(outfd, &ret, 1) != 1) - exit(3); + _exit(3); } - exit(0); + _exit(0); } static bool create_child(struct ptr_valid_batch *batch) @@ -271,16 +276,22 @@ bool ptr_valid_batch(struct ptr_valid_batch *batch, char *start, *end; bool ret; - if ((intptr_t)p & (alignment - 1)) + if ((intptr_t)p & (alignment - 1)) { + errno = EFAULT; return false; + } start = (void *)((intptr_t)p & ~(getpagesize() - 1)); end = (void *)(((intptr_t)p + size - 1) & ~(getpagesize() - 1)); /* We cache single page hits. */ if (start == end) { - if (batch->last && batch->last == start) + if (batch->last && batch->last == start + && batch->last_write == write) { + if (!batch->last_ok) + errno = EFAULT; return batch->last_ok; + } } if (batch->num_maps) @@ -291,8 +302,11 @@ bool ptr_valid_batch(struct ptr_valid_batch *batch, if (start == end) { batch->last = start; batch->last_ok = ret; + batch->last_write = write; } + if (!ret) + errno = EFAULT; return ret; } diff --git a/ccan/ccan/ptr_valid/ptr_valid.h b/ccan/ccan/ptr_valid/ptr_valid.h index 3871cad8e7e4..ad9a2e9c7c3b 100644 --- a/ccan/ccan/ptr_valid/ptr_valid.h +++ b/ccan/ccan/ptr_valid/ptr_valid.h @@ -80,6 +80,7 @@ struct ptr_valid_batch { int to_child, from_child; void *last; bool last_ok; + bool last_write; }; /** diff --git a/ccan/ccan/ptr_valid/test/run-batch-cache.c b/ccan/ccan/ptr_valid/test/run-batch-cache.c new file mode 100644 index 000000000000..50c8a750364e --- /dev/null +++ b/ccan/ccan/ptr_valid/test/run-batch-cache.c @@ -0,0 +1,40 @@ +/* Regression test: ptr_valid_batch()'s single-page cache ignores the + * read/write flag. On a read-only page, a cached read result makes + * ptr_valid_batch_write() claim the page is writable (and a cached + * write failure makes ptr_valid_batch_read() claim it is unreadable). + * Currently fails: not ok 2 and not ok 4. */ +#include +/* Include the C files directly. */ +#include +#include +#include +#include + +int main(void) +{ + char *page; + struct ptr_valid_batch batch; + + plan_tests(4); + alarm(10); + + page = mmap(NULL, getpagesize(), PROT_READ, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + if (page == MAP_FAILED) + plan_skip_all("mmap failed"); + + /* A cached read hit must not validate a write on the same page. */ + ptr_valid_batch_start(&batch); + ok1(ptr_valid_batch_read(&batch, page)); + ok1(!ptr_valid_batch_write(&batch, page)); + ptr_valid_batch_end(&batch); + + /* A cached write miss must not invalidate a read either. */ + ptr_valid_batch_start(&batch); + ok1(!ptr_valid_batch_write(&batch, page)); + ok1(ptr_valid_batch_read(&batch, page)); + ptr_valid_batch_end(&batch); + + munmap(page, getpagesize()); + return exit_status(); +} diff --git a/ccan/ccan/ptr_valid/test/run-child-flush.c b/ccan/ccan/ptr_valid/test/run-child-flush.c new file mode 100644 index 000000000000..379d4cb9e05d --- /dev/null +++ b/ccan/ccan/ptr_valid/test/run-child-flush.c @@ -0,0 +1,74 @@ +/* Regression test: run_child() exits via exit() instead of _exit(), + * so in the no-/proc/self/maps fallback (containers/chroots without + * /proc) the child flushes stdio buffers it inherited from the parent + * a second time. We force the fallback by undefining + * HAVE_PROC_SELF_MAPS (same code path as /proc being unavailable). + * Currently fails: not ok 2. */ +#include +#undef HAVE_PROC_SELF_MAPS +#define HAVE_PROC_SELF_MAPS 0 +/* Include the C files directly. */ +#include +#include +#include +#include +#include + +#if defined(__has_include) +#if __has_include() +#include +#define HAVE_VALGRIND_H 1 +#endif +#endif + +int main(void) +{ + char tmpl[] = "/tmp/ptr_valid-flush-XXXXXX"; + char *page; + int fd; + FILE *f; + long len; + +#ifdef HAVE_VALGRIND_H + /* valgrind's own child-exit path flushes the inherited buffer, + * masking the module's behavior. */ + if (RUNNING_ON_VALGRIND) { + plan_skip_all("valgrind perturbs stdio across fork"); + return exit_status(); + } +#endif + + plan_tests(2); + alarm(10); + + fd = mkstemp(tmpl); + if (fd < 0) + plan_skip_all("mkstemp failed"); + f = fdopen(fd, "w"); + if (!f) + plan_skip_all("fdopen failed"); + unlink(tmpl); + + /* Fully buffered stream with pending data at fork time. */ + setvbuf(f, NULL, _IOFBF, 4096); + fwrite("unflushed-data", 1, 14, f); + + page = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + if (page == MAP_FAILED) + plan_skip_all("mmap failed"); + + /* Creates the probing child; ptr_valid_batch_end() closes the + * pipe, the child sees EOF and calls exit(0), flushing its + * inherited copy of f's buffer. */ + ok1(ptr_valid_read(page)); + + /* The parent now flushes its own copy. */ + fflush(f); + len = ftell(f); + fclose(f); + munmap(page, getpagesize()); + + ok1(len == 14); + return exit_status(); +} diff --git a/ccan/ccan/ptr_valid/test/run-errno.c b/ccan/ccan/ptr_valid/test/run-errno.c new file mode 100644 index 000000000000..7cce2e22e913 --- /dev/null +++ b/ccan/ccan/ptr_valid/test/run-errno.c @@ -0,0 +1,43 @@ +/* Regression test: ptr_valid.h documents "Sets errno to EFAULT on + * failure", but the /proc/self/maps path and the alignment check + * return false without setting errno. Currently fails all 3 tests. */ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include + +int main(void) +{ + char *page; + + plan_tests(3); + alarm(10); + + page = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + if (page == MAP_FAILED) + plan_skip_all("mmap failed"); + munmap(page, getpagesize()); + + /* Unmapped pointer (maps path). */ + errno = 0; + ok1(!ptr_valid_read(page) && errno == EFAULT); + + /* Misaligned pointer. */ + errno = 0; + ok1(!ptr_valid(page + 1, getpagesize(), 1, false) && errno == EFAULT); + + /* Read-only page, write check (maps path). */ + page = mmap(NULL, getpagesize(), PROT_READ, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + if (page == MAP_FAILED) + plan_skip_all("mmap failed"); + errno = 0; + ok1(!ptr_valid_write(page) && errno == EFAULT); + munmap(page, getpagesize()); + + return exit_status(); +} diff --git a/ccan/ccan/ptr_valid/test/run-realloc-leak.c b/ccan/ccan/ptr_valid/test/run-realloc-leak.c new file mode 100644 index 000000000000..f358da16ec36 --- /dev/null +++ b/ccan/ccan/ptr_valid/test/run-realloc-leak.c @@ -0,0 +1,122 @@ +/* Regression test: grab() and add_map() overwrite their pointer with + * realloc()'s return value, so a failed realloc leaks the old buffer. + * We interpose malloc/realloc/free by macro (this file includes + * ptr_valid.c directly) and inject a failure at each growth call. + * Currently fails: not ok 1 and not ok 2. */ +#include +#include + +static void *live[64]; +static unsigned int nlive; +static size_t fail_realloc_size; + +static void track(void *p) +{ + if (p && nlive < sizeof(live)/sizeof(live[0])) + live[nlive++] = p; +} + +static void untrack(void *p) +{ + unsigned int i; + + for (i = 0; i < nlive; i++) { + if (live[i] == p) { + live[i] = live[--nlive]; + return; + } + } +} + +static void *my_malloc(size_t size) +{ + void *p; +#undef malloc + p = malloc(size); +#define malloc my_malloc + track(p); + return p; +} + +static void *my_realloc(void *old, size_t size) +{ + void *p; + uintptr_t o = (uintptr_t)old; + + if (fail_realloc_size && size == fail_realloc_size) + return NULL; +#undef realloc + p = realloc(old, size); +#define realloc my_realloc + if (p) { + untrack((void *)o); + track(p); + } + return p; +} + +static void my_free(void *p) +{ + if (p) + untrack(p); +#undef free + free(p); +#define free my_free +} + +#define malloc my_malloc +#define realloc my_realloc +#define free my_free +/* Include the C files directly. */ +#include +#undef malloc +#undef realloc +#undef free + +#include +#include +#include + +int main(void) +{ + char *region; + int i; + struct ptr_valid_batch batch; + + plan_tests(2); + alarm(20); + + /* Silence "defined but not used" when ptr_valid.c's + * HAVE_PROC_SELF_MAPS-less build calls no malloc at all. */ + (void)my_malloc; + (void)my_realloc; + (void)my_free; + + /* Split a 1000-page mapping into alternating RO/RW VMAs so + * /proc/self/maps exceeds grab()'s initial 16k buffer and has + * more entries than add_map()'s initial 16 slots. */ + region = mmap(NULL, 1000 * 4096, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + if (region == MAP_FAILED) + plan_skip_all("mmap failed"); + for (i = 0; i < 1000; i += 2) + if (mprotect(region + i * 4096, 4096, PROT_READ) != 0) + plan_skip_all("mprotect failed"); + + /* grab(): fail the 16384 -> 32768 buffer growth. */ + fail_realloc_size = 32768 + 1; + nlive = 0; + ptr_valid_batch_start(&batch); + ptr_valid_batch_end(&batch); + ok1(nlive == 0); + + /* add_map(): fail the 16 -> 32 entry array growth. */ + fail_realloc_size = sizeof(struct ptr_valid_map) * 32; + nlive = 0; + ptr_valid_batch_start(&batch); + ptr_valid_batch_end(&batch); + ok1(nlive == 0); + + munmap(region, 1000 * 4096); + return exit_status(); +} diff --git a/ccan/ccan/rbuf/rbuf.c b/ccan/ccan/rbuf/rbuf.c index cc10cf3d7f25..742373853dc9 100644 --- a/ccan/ccan/rbuf/rbuf.c +++ b/ccan/ccan/rbuf/rbuf.c @@ -62,8 +62,14 @@ void *rbuf_fill_all(struct rbuf *rbuf) void *rbuf_fill(struct rbuf *rbuf) { if (!rbuf_len(rbuf)) { - if (get_more(rbuf) < 0) + ssize_t r = get_more(rbuf); + if (r < 0) + return NULL; + /* EOF: documented NULL with errno 0. */ + if (r == 0) { + errno = 0; return NULL; + } } return rbuf_start(rbuf); } diff --git a/ccan/ccan/rbuf/test/run-fill-eof.c b/ccan/ccan/rbuf/test/run-fill-eof.c new file mode 100644 index 000000000000..e8c9d8e10abc --- /dev/null +++ b/ccan/ccan/rbuf/test/run-fill-eof.c @@ -0,0 +1,76 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Regression test: rbuf.h documents for rbuf_fill: + * "If there is nothing more to read, it will return NULL with errno set + * to 0", and the documented example loops while (rbuf_fill(&in)). + * The current code returns rbuf_start() (non-NULL) at EOF, so that loop + * never terminates. */ +int main(void) +{ + struct rbuf in; + int fd; + unsigned int iters; + void *p; + + plan_tests(7); + alarm(10); + + fd = open("run-fill-eof-file", O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (write(fd, "hello world\n", 12) != 12) + abort(); + close(fd); + fd = open("run-fill-eof-empty", O_WRONLY|O_CREAT|O_TRUNC, 0600); + close(fd); + + /* The documented loop pattern must terminate at EOF. */ + if (!rbuf_open(&in, "run-fill-eof-file", NULL, 0, membuf_realloc)) + abort(); + errno = EDOM; + iters = 0; + while (rbuf_fill(&in)) { + rbuf_consume(&in, rbuf_len(&in)); + if (++iters > 100) + break; + } + ok1(iters <= 100); + ok1(errno == 0); + close(in.fd); + free(rbuf_cleanup(&in)); + + /* Direct: after the last byte is consumed, rbuf_fill is NULL/EOF. */ + fd = open("run-fill-eof-file", O_RDONLY); + rbuf_init(&in, fd, NULL, 0, membuf_realloc); + p = rbuf_fill(&in); + ok1(p != NULL); + rbuf_consume(&in, rbuf_len(&in)); + errno = EDOM; + p = rbuf_fill(&in); + ok1(p == NULL); + ok1(errno == 0); + close(in.fd); + free(rbuf_cleanup(&in)); + + /* Empty file: the very first fill is already EOF. */ + fd = open("run-fill-eof-empty", O_RDONLY); + rbuf_init(&in, fd, NULL, 0, membuf_realloc); + errno = EDOM; + p = rbuf_fill(&in); + ok1(p == NULL); + ok1(errno == 0); + close(in.fd); + free(rbuf_cleanup(&in)); + + unlink("run-fill-eof-file"); + unlink("run-fill-eof-empty"); + + return exit_status(); +} diff --git a/ccan/ccan/rune/coding.c b/ccan/ccan/rune/coding.c index 495d37c34e31..016b0ac9e32f 100644 --- a/ccan/ccan/rune/coding.c +++ b/ccan/ccan/rune/coding.c @@ -206,7 +206,8 @@ bool rune_condition_is_valid(enum rune_condition cond) size_t rune_altern_fieldname_len(const char *alternstr, size_t alternstrlen) { for (size_t i = 0; i < alternstrlen; i++) { - if (cispunct(alternstr[i]) && alternstr[i] != '_') + if (cispunct(alternstr[i]) && alternstr[i] != '_' + && alternstr[i] != '-' && alternstr[i] != '.') return i; } return alternstrlen; diff --git a/ccan/ccan/rune/rune.c b/ccan/ccan/rune/rune.c index 7937f8cb5329..4a6dd52092e1 100644 --- a/ccan/ccan/rune/rune.c +++ b/ccan/ccan/rune/rune.c @@ -70,6 +70,10 @@ struct rune *rune_dup(const tal_t *ctx, const struct rune *rune TAKES) return tal_steal(ctx, (struct rune *)rune); dup = tal_dup(ctx, struct rune, rune); + if (rune->unique_id) + dup->unique_id = tal_strdup(dup, rune->unique_id); + if (rune->version) + dup->version = tal_strdup(dup, rune->version); dup->restrs = tal_arr(dup, struct rune_restr *, tal_count(rune->restrs)); for (size_t i = 0; i < tal_count(rune->restrs); i++) { dup->restrs[i] = rune_restr_dup(dup->restrs, @@ -287,11 +291,16 @@ static int lexo_order(const char *fieldval_str, size_t fieldval_strlen, const char *alt) { - int ret = strncmp(fieldval_str, alt, fieldval_strlen); - - /* If alt is same but longer, fieldval is < */ - if (ret == 0 && strlen(alt) > fieldval_strlen) - ret = -1; + size_t altlen = strlen(alt); + size_t minlen = fieldval_strlen < altlen ? fieldval_strlen : altlen; + int ret = memcmp(fieldval_str, alt, minlen); + + if (ret == 0) { + if (fieldval_strlen < altlen) + ret = -1; + else if (fieldval_strlen > altlen) + ret = 1; + } return ret; } diff --git a/ccan/ccan/rune/test/run-dup-unique-id.c b/ccan/ccan/rune/test/run-dup-unique-id.c new file mode 100644 index 000000000000..930221f41d5f --- /dev/null +++ b/ccan/ccan/rune/test/run-dup-unique-id.c @@ -0,0 +1,63 @@ +/* Regression test for audit finding: rune_dup() shallow-copies the + * unique_id and version pointers (rune.c:65-79), so the "copy" shares + * tal-owned strings with the original. Freeing the original leaves + * the copy with dangling unique_id/version pointers (heap-use-after-free + * under ASan; garbage contents in plain builds once the heap is reused). + * + * rune_dup is documented as "Copy a rune." (rune.h:99); a copy whose + * lifetime depends on the original violates that contract. + * + * This test must pass both in plain builds and under ASan once fixed. */ +#include +#include +#include +#include +#include + +int main(void) +{ + static const u8 secret_zero[16]; + struct rune *master, *rune, *dup; + char *str; + + alarm(10); + plan_tests(9); + + master = rune_new(NULL, secret_zero, sizeof(secret_zero), "1"); + rune = rune_derive_start(NULL, master, "uid1"); + dup = rune_dup(NULL, rune); + + ok1(dup != NULL); + ok1(streq(dup->unique_id, "uid1")); + ok1(streq(dup->version, "1")); + + /* The copy must own its strings: they must not be tal children of + * the original rune (which the caller is entitled to free). */ + ok1(tal_parent(dup->unique_id) != (tal_t *)rune); + ok1(tal_parent(dup->version) != (tal_t *)rune); + + /* Free the original: the documented copy must remain valid. */ + tal_free(rune); + + /* Try to get the freed strings reused by other allocations, so the + * dangling pointers point at garbage in plain builds too. */ + for (size_t i = 0; i < 64; i++) + tal_free(tal_strdup(NULL, "XXXX")); + + /* Under ASan these uses of dup abort with heap-use-after-free. */ + ok1(streq(dup->unique_id, "uid1")); + ok1(streq(dup->version, "1")); + + /* rune_eq reads unique_id/version of both sides (runestr_eq). */ + ok1(rune_eq(dup, dup)); + + /* A roundtrip through the string form must still work. */ + str = rune_to_string(NULL, dup); + ok1(rune_from_string(NULL, str) != NULL); + + tal_free(dup); + tal_free(str); + tal_free(master); + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/rune/test/run-fieldname-punct.c b/ccan/ccan/rune/test/run-fieldname-punct.c new file mode 100644 index 000000000000..7b78aeec7afa --- /dev/null +++ b/ccan/ccan/rune/test/run-fieldname-punct.c @@ -0,0 +1,66 @@ +/* Regression test for audit finding: rune.h documents that an altern + * fieldname may contain "alphanumerics, '.', '-' and '_'" + * (rune.h:111-113), and rune_altern_new() accepts such fieldnames + * without complaint, but rune_altern_fieldname_len() (coding.c:206-213) + * stops at ANY punctuation except '_' — so a rune built with a '.' + * or '-' in the fieldname encodes fine yet cannot be parsed back: + * rune_to_string()/rune_to_base64() output is rejected by + * rune_from_string()/rune_from_base64() (roundtrip failure). + * + * This test must pass once the encode/decode sides agree with the + * documented fieldname charset. */ +#include +#include +#include +#include +#include + +int main(void) +{ + static const u8 secret_zero[16]; + struct rune *rune, *back; + struct rune_restr *restr; + char *str, *b64; + + alarm(10); + plan_tests(7); + + rune = rune_new(NULL, secret_zero, sizeof(secret_zero), NULL); + restr = rune_restr_new(NULL); + rune_restr_add_altern(restr, take(rune_altern_new(NULL, "a.b-c", + RUNE_COND_EQUAL, "7"))); + ok1(rune_add_restr(rune, take(restr))); + + str = rune_to_string(NULL, rune); + ok1(str != NULL); + + /* Documented-valid rune must survive the string roundtrip. */ + back = rune_from_string(NULL, str); + ok1(back != NULL); + if (back) + ok1(rune_eq(rune, back)); + else + fail("rune_from_string returned NULL"); + tal_free(back); + + /* And the base64 roundtrip. */ + b64 = rune_to_base64(NULL, rune); + back = rune_from_base64(NULL, b64); + ok1(back != NULL); + if (back) + ok1(rune_eq(rune, back)); + else + fail("rune_from_base64 returned NULL"); + tal_free(back); + + /* Direct restriction parsing of a documented-valid fieldname. */ + restr = rune_restr_from_string(NULL, "a.b-c=7", strlen("a.b-c=7")); + ok1(restr != NULL); + tal_free(restr); + + tal_free(str); + tal_free(b64); + tal_free(rune); + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/rune/test/run-lexo-embedded-nul.c b/ccan/ccan/rune/test/run-lexo-embedded-nul.c new file mode 100644 index 000000000000..bd3a14523615 --- /dev/null +++ b/ccan/ccan/rune/test/run-lexo-embedded-nul.c @@ -0,0 +1,49 @@ +/* Regression test for audit finding: lexo_order() (rune.c:286-296) uses + * strncmp(), which stops comparing at the first NUL. Every other string + * condition (EQUAL, NOT_EQUAL, BEGINS, ENDS, CONTAINS) uses NUL-safe + * counted helpers (memeqstr, memstarts_str, memends_str, memmem), so + * counted field values containing embedded NULs are in-contract for + * rune_alt_single_str(). With an embedded NUL, lexo_order reports + * "equal" when the altern value is a proper prefix of the field value + * up to the NUL, giving wrong answers for RUNE_COND_LEXO_BEFORE and + * RUNE_COND_LEXO_AFTER (authorization decisions). + * + * This test must pass once lexo_order compares counted bytes. */ +#include +#include +#include +#include +#include + +int main(void) +{ + /* fieldval = "abc\0X": byte-wise it sorts strictly after "abc" + * ("abc" is a proper prefix). */ + static const char fieldval[] = { 'a', 'b', 'c', 0, 'X' }; + struct rune_altern *alt; + const char *err; + + alarm(10); + plan_tests(3); + + /* fieldval > "abc": LEXO_AFTER must pass (err == NULL). */ + alt = rune_altern_new(NULL, "f", RUNE_COND_LEXO_AFTER, "abc"); + err = rune_alt_single_str(NULL, alt, fieldval, sizeof(fieldval)); + ok1(err == NULL); + tal_free(alt); + + /* fieldval > "abc": LEXO_BEFORE must fail (err != NULL). */ + alt = rune_altern_new(NULL, "f", RUNE_COND_LEXO_BEFORE, "abc"); + err = rune_alt_single_str(NULL, alt, fieldval, sizeof(fieldval)); + ok1(err != NULL); + tal_free(alt); + + /* Sanity: genuine equality passes neither strict condition. */ + alt = rune_altern_new(NULL, "f", RUNE_COND_LEXO_AFTER, "abc"); + err = rune_alt_single_str(NULL, alt, "abc", 3); + ok1(err != NULL); + tal_free(alt); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/short_types/_info b/ccan/ccan/short_types/_info index 909e4e3aed0e..36ec05582bc9 100644 --- a/ccan/ccan/short_types/_info +++ b/ccan/ccan/short_types/_info @@ -40,7 +40,7 @@ * *size_total += size; * } * - * #define EVALUATE(psx, short, pt, st, t) \ + * #define EVALUATE(psx, sht, pt, st, t) \ * evaluate(sizeof(psx), stringify(psx), stringify(sht), pt, st, t) * * int main(void) diff --git a/ccan/ccan/str/base32/base32.c b/ccan/ccan/str/base32/base32.c index 6145da300765..91849d31d131 100644 --- a/ccan/ccan/str/base32/base32.c +++ b/ccan/ccan/str/base32/base32.c @@ -153,7 +153,7 @@ bool base32_encode(const void *buf, size_t bufsize, char *dest, size_t destsize) destsize -= 8; dest += 8; } - if (destsize != 1) + if (destsize < 1) return false; *dest = '\0'; return true; diff --git a/ccan/ccan/str/base32/test/run-encode-oversized-dest.c b/ccan/ccan/str/base32/test/run-encode-oversized-dest.c new file mode 100644 index 000000000000..8eaf1ba56f98 --- /dev/null +++ b/ccan/ccan/str/base32/test/run-encode-oversized-dest.c @@ -0,0 +1,39 @@ +/* Auditor-added regression test (temporary) for audit finding F1: + * base32.h documents "@destsize: the max size of the string" and + * "Returns true if the string, including terminator, fits in @destsize", + * but base32_encode() requires destsize to be exactly + * base32_str_size(bufsize) and returns false for larger buffers. + * This test encodes the documented contract; it FAILS against the + * current implementation and must PASS after repair. */ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include + +int main(void) +{ + char dest[100]; + + alarm(10); + plan_tests(6); + + memset(dest, 0xAA, sizeof(dest)); + /* 9-byte encoding of "f" fits in a 100-byte dest. */ + ok1(base32_encode("f", 1, dest, sizeof(dest))); + ok1(strcmp(dest, "MY======") == 0); + + memset(dest, 0xAA, sizeof(dest)); + /* One extra byte of room must also be fine. */ + ok1(base32_encode("fo", 2, dest, base32_str_size(2) + 1)); + ok1(strcmp(dest, "MZXQ====") == 0); + + memset(dest, 0xAA, sizeof(dest)); + /* Empty input with room to spare. */ + ok1(base32_encode("", 0, dest, sizeof(dest))); + ok1(dest[0] == '\0'); + + return exit_status(); +} diff --git a/ccan/ccan/str/hex/_info b/ccan/ccan/str/hex/_info index d70a1425e845..0a597516e320 100644 --- a/ccan/ccan/str/hex/_info +++ b/ccan/ccan/str/hex/_info @@ -18,7 +18,7 @@ * for (i = 1; i < argc; i++) { * char str[hex_str_size(strlen(argv[i]))]; * - * hex_encode(str, sizeof(str), argv[i], strlen(argv[i])); + * hex_encode(argv[i], strlen(argv[i]), str, sizeof(str)); * printf("%s ", str); * } * printf("\n"); diff --git a/ccan/ccan/str/str.c b/ccan/ccan/str/str.c index a9245c1742ec..a012c1d03aec 100644 --- a/ccan/ccan/str/str.c +++ b/ccan/ccan/str/str.c @@ -5,6 +5,9 @@ size_t strcount(const char *haystack, const char *needle) { size_t i = 0, nlen = strlen(needle); + if (nlen == 0) + return 0; + while ((haystack = strstr(haystack, needle)) != NULL) { i++; haystack += nlen; diff --git a/ccan/ccan/str/test/run-strcount-empty-needle.c b/ccan/ccan/str/test/run-strcount-empty-needle.c new file mode 100644 index 000000000000..f5824fb1f96f --- /dev/null +++ b/ccan/ccan/str/test/run-strcount-empty-needle.c @@ -0,0 +1,20 @@ +/* Regression test (2026-08-05 audit, see audit-findings/str.md): + * strcount(haystack, "") must not hang. strstr(haystack, "") returns + * haystack, so without the empty-needle guard in strcount() the loop + * never advances. The alarm() bound turns a regression into a visible + * test failure (killed by SIGALRM) instead of a stuck test run. */ +#include +#include +#include +#include +#include + +int main(void) +{ + plan_tests(1); + alarm(10); + /* Fixed semantics: an empty needle occurs nowhere (0). The point + * is that strcount() returns at all. */ + ok1(strcount("abc", "") == 0); + return exit_status(); +} diff --git a/ccan/ccan/strmap/_info b/ccan/ccan/strmap/_info index eba8fe444ae7..094521af3797 100644 --- a/ccan/ccan/strmap/_info +++ b/ccan/ccan/strmap/_info @@ -53,6 +53,7 @@ int main(int argc, char *argv[]) if (strcmp(argv[1], "depends") == 0) { printf("ccan/ilog\n" + "ccan/mem\n" "ccan/short_types\n" "ccan/str\n" "ccan/tcon\n" diff --git a/ccan/ccan/strmap/strmap.c b/ccan/ccan/strmap/strmap.c index 16a30e036ad4..c711beb9ebae 100644 --- a/ccan/ccan/strmap/strmap.c +++ b/ccan/ccan/strmap/strmap.c @@ -2,9 +2,11 @@ #include #include #include +#include #include #include #include +#include #include struct node { @@ -42,7 +44,7 @@ void *strmap_getn_(const struct strmap *map, /* Not empty map? */ if (map->u.n) { n = closest((struct strmap *)map, member, memberlen); - if (!strncmp(member, n->u.s, memberlen) && !n->u.s[memberlen]) + if (memeqstr(member, memberlen, n->u.s)) return n->v; } errno = ENOENT; @@ -178,26 +180,119 @@ char *strmap_del_(struct strmap *map, const char *member, void **valuep) return (char *)ret; } -static bool iterate(struct strmap n, - bool (*handle)(const char *, void *, void *), - const void *data) +/* Defer child[1] of a node we're descending past. */ +static void iter_push(struct strmap_iter *it, struct strmap *slot) { - if (n.v) - return handle(n.u.s, n.v, (void *)data); + if (STRMAP_NUM_ITER_PARENTS == 0) { + it->dropped = true; + return; + } + if (it->num_parents == STRMAP_NUM_ITER_PARENTS) { + /* Full: drop the *shallowest* deferral (kept in-order by + * the slow path once the stack runs out). */ + memmove(&it->parents[0], &it->parents[1], + sizeof(it->parents[0]) * (STRMAP_NUM_ITER_PARENTS - 1)); + it->num_parents--; + it->dropped = true; + } + it->parents[it->num_parents++] = slot; +} + +/* Descend leftmost from *slot, deferring child[1]s, and yield the leaf. */ +static const char *iter_descend(struct strmap_iter *it, struct strmap *slot, + void **valuep) +{ + while (!slot->v) { + iter_push(it, &slot->u.n->child[1]); + slot = &slot->u.n->child[0]; + } + *valuep = slot->v; + return slot->u.s; +} + +/* Successor of cur by value: O(depth), no stack. */ +static const char *iter_successor(const struct strmap *map, + const char *cur, void **valuep) +{ + size_t len = strlen(cur); + const u8 *bytes = (const u8 *)cur; + struct strmap n, cand; + bool have_cand = false; + + n = *(struct strmap *)map; + while (!n.v) { + u8 c = 0, direction; + + if (n.u.n->byte_num < len) + c = bytes[n.u.n->byte_num]; + direction = (c >> n.u.n->bit_num) & 1; + if (direction == 0) { + /* Everything in child[1] sorts after child[0]. */ + cand = n.u.n->child[1]; + have_cand = true; + } + n = n.u.n->child[direction]; + } + + if (!have_cand) + return NULL; + + /* Leftmost member of the deepest candidate subtree. */ + while (!cand.v) + cand = cand.u.n->child[0]; + *valuep = cand.v; + return cand.u.s; +} + +const char *strmap_iter_first_(struct strmap_iter *it, + const struct strmap *map, void **valuep) +{ + it->num_parents = 0; + it->dropped = false; + it->slow_mode = false; + + if (!map->u.n) + return NULL; + + return iter_descend(it, (struct strmap *)map, valuep); +} + +const char *strmap_iter_next_(struct strmap_iter *it, + const struct strmap *map, const char *cur, + void **valuep) +{ + struct strmap *slot; - return iterate(n.u.n->child[0], handle, data) - && iterate(n.u.n->child[1], handle, data); + if (!it->slow_mode) { + if (it->num_parents != 0) { + slot = it->parents[--it->num_parents]; + return iter_descend(it, slot, valuep); + } + if (!it->dropped) + return NULL; + /* We dropped deferrals past STRMAP_NUM_ITER_PARENTS; + * from here on, find successors by re-descent. */ + it->dropped = false; + it->slow_mode = true; + } + + return iter_successor(map, cur, valuep); } void strmap_iterate_(const struct strmap *map, bool (*handle)(const char *, void *, void *), const void *data) { - /* Empty map? */ - if (!map->u.n) - return; - - iterate(*map, handle, data); + struct strmap_iter it; + const char *m; + void *v; + + for (m = strmap_iter_first_(&it, map, &v); + m; + m = strmap_iter_next_(&it, map, m, &v)) { + if (!handle(m, v, (void *)data)) + break; + } } const struct strmap *strmap_prefix_(const struct strmap *map, @@ -235,6 +330,7 @@ const struct strmap *strmap_prefix_(const struct strmap *map, return top; } +/* Recursive fallback for strmap_clear_'s OOM path. */ static void clear(struct strmap n) { if (!n.v) { @@ -246,7 +342,75 @@ static void clear(struct strmap n) void strmap_clear_(struct strmap *map) { - if (map->u.n) - clear(*map); + uintptr_t inline_stack[STRMAP_NUM_ITER_PARENTS]; + uintptr_t *stack = inline_stack; + size_t num = 0, max = STRMAP_NUM_ITER_PARENTS; + uintptr_t cur; + bool have_cur; + + if (!map->u.n) + return; + + /* Post-order without recursion: slot pointers tagged in their + * low bits (0 = visit child[0], 1 = visit child[1], 2 = free). */ + cur = (uintptr_t)map; + have_cur = true; + + while (have_cur || num) { + struct strmap *slot; + unsigned int tag; + + if (!have_cur) + cur = stack[--num]; + have_cur = false; + slot = (struct strmap *)(cur & ~(uintptr_t)3); + tag = cur & 3; + + if (slot->v) { + /* Leaf: caller-owned, keep. */ + continue; + } + if (tag == 2) { + free(slot->u.n); + continue; + } + + /* tag 0 or 1: requeue for the next phase, then descend. */ + { + struct strmap *child = &slot->u.n->child[tag]; + + if (num == max) { + uintptr_t *ns; + size_t nmax = max ? max * 2 : 64; + + if (stack == inline_stack) { + ns = malloc(nmax * sizeof(*ns)); + if (ns) + memcpy(ns, stack, + num * sizeof(*ns)); + } else { + ns = realloc(stack, nmax * sizeof(*ns)); + } + if (ns) { + stack = ns; + max = nmax; + } else { + /* OOM: finish this subtree + * recursively. */ + clear(*child); + if (tag == 0) + clear(slot->u.n->child[1]); + free(slot->u.n); + continue; + } + } + stack[num++] = (uintptr_t)slot | (tag + 1); + cur = (uintptr_t)child; + have_cur = true; + } + } + + if (stack != inline_stack) + free(stack); map->u.n = NULL; } diff --git a/ccan/ccan/strmap/strmap.h b/ccan/ccan/strmap/strmap.h index 8724c31dcbb2..800f78c70b00 100644 --- a/ccan/ccan/strmap/strmap.h +++ b/ccan/ccan/strmap/strmap.h @@ -5,6 +5,7 @@ #include #include #include +#include /** * struct strmap - representation of a string map @@ -204,6 +205,75 @@ void strmap_iterate_(const struct strmap *map, bool (*handle)(const char *, void *, void *), const void *data); +#ifndef STRMAP_NUM_ITER_PARENTS +#define STRMAP_NUM_ITER_PARENTS 16 +#endif + +/** + * struct strmap_iter - state for strmap_iter_first/strmap_iter_next. + * + * This is exposed so you can declare it on the stack. It holds up to + * STRMAP_NUM_ITER_PARENTS deferred subtrees; in trees deeper than + * that, iteration falls back to re-descending from the root (an + * O(depth) successor search per step), so memory use stays bounded + * for arbitrarily deep trees. + */ +struct strmap_iter { + struct strmap *parents[STRMAP_NUM_ITER_PARENTS]; + uint16_t num_parents; + bool dropped; + bool slow_mode; +}; + +/** + * strmap_iter_first - begin an ordered iteration over a map. + * @it: the iterator to initialize. + * @map: the typed strmap to iterate. + * @valuep: a pointer to a value to fill in. + * + * Returns the first member, and sets *@valuep, or returns NULL if the + * map is empty. You should not alter the map during iteration! + * + * Example: + * static void dump_map_iter(const STRMAP(int *) *map) + * { + * struct strmap_iter it; + * const char *m; + * int *v; + * + * for (m = strmap_iter_first(&it, map, &v); + * m; + * m = strmap_iter_next(&it, map, m, &v)) + * printf("%s=>%i\n", m, *v); + * } + */ +#define strmap_iter_first(it, map, valuep) \ + strmap_iter_first_((it), \ + tcon_unwrap(tcon_check_ptr((map), canary, \ + (valuep))), \ + (void *)(valuep)) +const char *strmap_iter_first_(struct strmap_iter *it, + const struct strmap *map, void **valuep); + +/** + * strmap_iter_next - continue an ordered iteration. + * @it: the iterator, initialized by strmap_iter_first(). + * @map: the typed strmap. + * @cur: the member the iteration is currently on. + * @valuep: a pointer to a value to fill in. + * + * Returns the next member, and sets *@valuep, or NULL at the end of + * the map. + */ +#define strmap_iter_next(it, map, cur, valuep) \ + strmap_iter_next_((it), \ + tcon_unwrap(tcon_check_ptr((map), canary, \ + (valuep))), \ + (cur), (void *)(valuep)) +const char *strmap_iter_next_(struct strmap_iter *it, + const struct strmap *map, const char *cur, + void **valuep); + /** * strmap_prefix - return a submap matching a prefix * @map: the map. diff --git a/ccan/ccan/strmap/test/run-getn-embedded-nul.c b/ccan/ccan/strmap/test/run-getn-embedded-nul.c new file mode 100644 index 000000000000..9f5650e3dbec --- /dev/null +++ b/ccan/ccan/strmap/test/run-getn-embedded-nul.c @@ -0,0 +1,67 @@ +/* Regression test for strmap_getn_ reading past the stored key (and + * falsely matching) when the counted member buffer contains an embedded + * NUL before memberlen (ccan/strmap/strmap.c:45). + * + * Correct behavior: a memberlen-byte buffer can only match a key of + * exactly memberlen bytes; keys are NUL-terminated C strings, so a + * buffer with an embedded NUL must never match. The current code's + * strncmp() stops at the common NUL, then evaluates n->u.s[memberlen], + * which (a) reads past the end of a shorter stored key and (b) if that + * out-of-bounds byte happens to be 0, returns a false match. + * + * Subtests 1-3 use a key stored in a zero-padded buffer so the current + * code deterministically returns a WRONG match in a plain build. + * Subtests 4-5 use a tightly allocated key; today they read 2 bytes past + * the 3-byte allocation (ASan: heap-buffer-overflow at strmap.c:45). + */ +#include +#include +#include +#include +#include +#include +#include + +int main(void) +{ + STRMAP(char *) map; + static char keybuf[16] = "hi"; /* zero-padded: current code reads [5] */ + static const char member[5] = { 'h', 'i', 0, 0, 0 }; + char *tight; + void *v; + + alarm(10); + plan_tests(6); + + strmap_init(&map); + + /* Empty map: ENOENT, no read at all. */ + errno = 0; + ok1(strmap_getn(&map, member, 5) == NULL); + ok1(errno == ENOENT); + + strmap_add(&map, keybuf, keybuf); + + /* The buffer's first 5 bytes contain an embedded NUL; no 5-byte + * key can exist in the map, so this must be an ENOENT miss. */ + errno = 0; + v = strmap_getn(&map, member, 5); + ok1(v == NULL); + ok1(errno == ENOENT); + + /* Same, with a tightly allocated key: today strmap.c:45 reads + * n->u.s[5], 2 bytes past the 3-byte allocation. */ + tight = strdup("yo"); + strmap_add(&map, tight, tight); + { + static const char member2[5] = { 'y', 'o', 0, 0, 0 }; + errno = 0; + ok1(strmap_getn(&map, member2, 5) == NULL); + ok1(errno == ENOENT); + } + + strmap_clear(&map); + free(tight); + + return exit_status(); +} diff --git a/ccan/ccan/strmap/test/run-iter.c b/ccan/ccan/strmap/test/run-iter.c new file mode 100644 index 000000000000..8ea693d60d30 --- /dev/null +++ b/ccan/ccan/strmap/test/run-iter.c @@ -0,0 +1,134 @@ +/* Tests for strmap_iter_first/strmap_iter_next and the non-recursive + * strmap_clear_: order must match strmap_iterate (checked against + * sorted order by run-order.c), and deep trees must not crash. + * Compiled with varying STRMAP_NUM_ITER_PARENTS to exercise the + * slow (successor re-descent) path. */ +#include +#include +#include +#include +#include + +/* A staircase of 8 splits per byte gives a chain of depth ~N. */ +#define NDEEP 3000 + +static STRMAP(char *) map; +static const char *members[600]; +static unsigned int nmembers; + +static bool grab(const char *member, char *value, void *p) +{ + (void)value; + (void)p; + members[nmembers++] = member; + return true; +} + +int main(void) +{ + unsigned int i, n; + const char *m; + char *v; + struct strmap_iter it; + bool order_ok, slow_seen; + + alarm(60); + plan_tests(7); + + /* Empty map. */ + strmap_init(&map); + ok1(strmap_iter_first(&it, &map, &v) == NULL); + + /* Random-ish set (xorshift), with shared prefixes. */ + { + static char buf[500][16]; + static char val[500]; + uint64_t r = 0x0fedcba987654321ULL; + for (i = 0; i < 500; i++) { + unsigned int j, len; + r ^= r << 13; r ^= r >> 7; r ^= r << 17; + len = r % 12 + 1; + for (j = 0; j < len; j++) { + r ^= r << 13; r ^= r >> 7; r ^= r << 17; + buf[i][j] = 'a' + (r % 26); + } + buf[i][len] = '\0'; + if (i % 3 == 0 && i > 0) + buf[i][0] = buf[i-1][0]; + val[i] = (char)i; + strmap_add(&map, buf[i], &val[i]); + } + } + + /* Reference order via the cursor-based iterate. */ + nmembers = 0; + strmap_iterate(&map, grab, NULL); + n = nmembers; + + order_ok = true; + slow_seen = false; + i = 0; + for (m = strmap_iter_first(&it, &map, &v); + m; + m = strmap_iter_next(&it, &map, m, &v)) { + if (it.slow_mode) + slow_seen = true; + if (i >= n || strcmp(m, members[i]) != 0) + order_ok = false; + i++; + } + ok1(i == n); + ok1(order_ok); + /* With the default 16 parents this map should not need the + * slow path; with a tiny override it must have been used. */ + if (STRMAP_NUM_ITER_PARENTS >= 16) + ok1(!slow_seen); + else + ok1(slow_seen); + + /* Check values track members: iterate and verify each value. */ + { + bool vals_ok = true; + for (m = strmap_iter_first(&it, &map, &v); + m; + m = strmap_iter_next(&it, &map, m, &v)) { + if (strmap_get(&map, m) != v) + vals_ok = false; + } + ok1(vals_ok); + } + strmap_clear(&map); + ok1(strmap_empty(&map)); + + /* Deep staircase: iterate and clear without stack overflow. */ + { + static char deep[NDEEP][400]; + static char dval; + unsigned int j; + for (i = 0; i < NDEEP; i++) { + unsigned int len = i / 8 + 1; + for (j = 0; j < len - 1; j++) + deep[i][j] = '\xff'; + if (i % 8 == 0) + deep[i][len-1] = 0x01; + else + deep[i][len-1] = (char)(unsigned char)(0xff << (i % 8)); + deep[i][len] = '\0'; + strmap_add(&map, deep[i], &dval); + } + n = 0; + order_ok = true; + for (m = strmap_iter_first(&it, &map, &v); + m; + m = strmap_iter_next(&it, &map, m, &v)) { + if (n > 0 && strcmp(m, members[0]) <= 0) + order_ok = false; + members[0] = m; + n++; + } + strmap_clear(&map); + ok1(n == NDEEP && order_ok && strmap_empty(&map)); + } + + return exit_status(); +} diff --git a/ccan/ccan/strset/strset.c b/ccan/ccan/strset/strset.c index 06b0d7a76c35..5d5dc86dd82f 100644 --- a/ccan/ccan/strset/strset.c +++ b/ccan/ccan/strset/strset.c @@ -19,6 +19,7 @@ #include #include #include +#include #include struct node { @@ -228,26 +229,127 @@ char *strset_del(struct strset *set, const char *member) return (char *)ret; } -static bool iterate(struct strset n, - bool (*handle)(const char *, void *), const void *data) +void strset_iterate_(const struct strset *set, + bool (*handle)(const char *, void *), const void *data) { - if (n.u.s[0]) - return handle(n.u.s, (void *)data); - if (unlikely(n.u.n->byte_num == (size_t)-1)) - return handle(n.u.n->child[0].u.s, (void *)data); + struct strset_iter it; + const char *m; - return iterate(n.u.n->child[0], handle, data) - && iterate(n.u.n->child[1], handle, data); + for (m = strset_iter_first(&it, set); + m; + m = strset_iter_next(&it, set, m)) { + if (!handle(m, (void *)data)) + break; + } } -void strset_iterate_(const struct strset *set, - bool (*handle)(const char *, void *), const void *data) +/* Defer child[1] of a node we're descending past. */ +static void iter_push(struct strset_iter *it, struct strset *slot) { - /* Empty set? */ - if (!set->u.n) + if (STRSET_NUM_ITER_PARENTS == 0) { + it->dropped = true; return; + } + if (it->num_parents == STRSET_NUM_ITER_PARENTS) { + /* Full: drop the *shallowest* deferral (kept in-order by + * the slow_mode path once the stack runs out). */ + memmove(&it->parents[0], &it->parents[1], + sizeof(it->parents[0]) * (STRSET_NUM_ITER_PARENTS - 1)); + it->num_parents--; + it->dropped = true; + } + it->parents[it->num_parents++] = slot; +} - iterate(*set, handle, data); +/* Descend leftmost from *slot, deferring child[1]s, and yield the leaf. */ +static const char *iter_descend(struct strset_iter *it, struct strset *slot) +{ + while (!slot->u.s[0]) { + /* Empty-string node: the string is child[0]. */ + if (unlikely(slot->u.n->byte_num == (size_t)-1)) { + slot = &slot->u.n->child[0]; + break; + } + iter_push(it, &slot->u.n->child[1]); + slot = &slot->u.n->child[0]; + } + return slot->u.s; +} + +/* Successor of cur by value: O(depth), no stack. */ +static const char *iter_successor(const struct strset *set, + const char *cur) +{ + size_t len = strlen(cur); + const u8 *bytes = (const u8 *)cur; + struct strset n, cand; + bool have_cand = false; + + n = *(struct strset *)set; + while (!n.u.s[0]) { + u8 c = 0, direction; + + /* Empty-string node: only holds "" in child[0]. */ + if (unlikely(n.u.n->byte_num == (size_t)-1)) + break; + if (n.u.n->byte_num < len) + c = bytes[n.u.n->byte_num]; + direction = (c >> n.u.n->bit_num) & 1; + if (direction == 0) { + /* Everything in child[1] sorts after child[0]. */ + cand = n.u.n->child[1]; + have_cand = true; + } + n = n.u.n->child[direction]; + } + + if (!have_cand) + return NULL; + + /* Leftmost member of the deepest candidate subtree. */ + while (!cand.u.s[0]) { + if (unlikely(cand.u.n->byte_num == (size_t)-1)) { + cand = cand.u.n->child[0]; + break; + } + cand = cand.u.n->child[0]; + } + return cand.u.s; +} + +const char *strset_iter_first(struct strset_iter *it, + const struct strset *set) +{ + it->num_parents = 0; + it->dropped = false; + it->slow_mode = false; + + if (!set->u.n) + return NULL; + + return iter_descend(it, (struct strset *)set); +} + +const char *strset_iter_next(struct strset_iter *it, + const struct strset *set, + const char *cur) +{ + struct strset *slot; + + if (likely(!it->slow_mode)) { + if (it->num_parents != 0) { + slot = it->parents[--it->num_parents]; + return iter_descend(it, slot); + } + if (!it->dropped) + return NULL; + /* We dropped deferrals past STRSET_NUM_ITER_PARENTS; + * from here on, find successors by re-descent. */ + it->dropped = false; + it->slow_mode = true; + } + + return iter_successor(set, cur); } const struct strset *strset_prefix(const struct strset *set, const char *prefix) @@ -290,6 +392,7 @@ const struct strset *strset_prefix(const struct strset *set, const char *prefix) return top; } +/* Recursive fallback for strset_clear's OOM path. */ static void clear(struct strset n) { if (!n.u.s[0]) { @@ -303,7 +406,81 @@ static void clear(struct strset n) void strset_clear(struct strset *set) { - if (set->u.n) - clear(*set); + uintptr_t inline_stack[STRSET_NUM_ITER_PARENTS]; + uintptr_t *stack = inline_stack; + size_t num = 0, max = STRSET_NUM_ITER_PARENTS; + uintptr_t cur; + bool have_cur; + + if (!set->u.n) + return; + + /* Post-order without recursion: slot pointers tagged in their + * low bits (0 = visit child[0], 1 = visit child[1], 2 = free). */ + cur = (uintptr_t)set; + have_cur = true; + + while (have_cur || num) { + struct strset *slot; + unsigned int tag; + + if (!have_cur) + cur = stack[--num]; + have_cur = false; + slot = (struct strset *)(cur & ~(uintptr_t)3); + tag = cur & 3; + + if (slot->u.s[0]) { + /* Leaf: caller-owned, keep. */ + continue; + } + if (unlikely(slot->u.n->byte_num == (size_t)-1)) { + /* Empty-string node: child[0] is the caller-owned + * string itself; child[1] is unused. */ + free(slot->u.n); + continue; + } + if (tag == 2) { + free(slot->u.n); + continue; + } + + /* tag 0 or 1: requeue for the next phase, then descend. */ + { + struct strset *child = &slot->u.n->child[tag]; + + if (num == max) { + uintptr_t *ns; + size_t nmax = max ? max * 2 : 64; + + if (stack == inline_stack) { + ns = malloc(nmax * sizeof(*ns)); + if (ns) + memcpy(ns, stack, + num * sizeof(*ns)); + } else { + ns = realloc(stack, nmax * sizeof(*ns)); + } + if (ns) { + stack = ns; + max = nmax; + } else { + /* OOM: finish this subtree + * recursively. */ + clear(*child); + if (tag == 0) + clear(slot->u.n->child[1]); + free(slot->u.n); + continue; + } + } + stack[num++] = (uintptr_t)slot | (tag + 1); + cur = (uintptr_t)child; + have_cur = true; + } + } + + if (stack != inline_stack) + free(stack); set->u.n = NULL; } diff --git a/ccan/ccan/strset/strset.h b/ccan/ccan/strset/strset.h index 9d6f1ae343f5..1141051a58b6 100644 --- a/ccan/ccan/strset/strset.h +++ b/ccan/ccan/strset/strset.h @@ -4,6 +4,7 @@ #include #include #include +#include /** * struct strset - representation of a string set @@ -141,6 +142,61 @@ void strset_clear(struct strset *set); void strset_iterate_(const struct strset *set, bool (*handle)(const char *, void *), const void *data); +#ifndef STRSET_NUM_ITER_PARENTS +#define STRSET_NUM_ITER_PARENTS 16 +#endif + +/** + * struct strset_iter - state for strset_iter_first/strset_iter_next. + * + * This is exposed so you can declare it on the stack. It holds up to + * STRSET_NUM_ITER_PARENTS deferred subtrees; in trees deeper than + * that, iteration falls back to re-descending from the root (an + * O(depth) successor search per step), so memory use stays bounded + * for arbitrarily deep trees. + */ +struct strset_iter { + struct strset *parents[STRSET_NUM_ITER_PARENTS]; + uint16_t num_parents; + bool dropped; + bool slow_mode; +}; + +/** + * strset_iter_first - begin an ordered iteration over a set. + * @it: the iterator to initialize. + * @set: the set. + * + * Returns the first member, or NULL if the set is empty. + * You should not alter the set during iteration! + * + * Example: + * static void dump_set_iter(const struct strset *set) + * { + * struct strset_iter it; + * const char *m; + * + * for (m = strset_iter_first(&it, set); + * m; + * m = strset_iter_next(&it, set, m)) + * printf("%s\n", m); + * } + */ +const char *strset_iter_first(struct strset_iter *it, + const struct strset *set); + +/** + * strset_iter_next - continue an ordered iteration. + * @it: the iterator, initialized by strset_iter_first(). + * @set: the set. + * @cur: the member the iteration is currently on. + * + * Returns the next member, or NULL at the end of the set. + */ +const char *strset_iter_next(struct strset_iter *it, + const struct strset *set, + const char *cur); + /** * strset_prefix - return a subset matching a prefix diff --git a/ccan/ccan/strset/test/run-deep-recursion.c b/ccan/ccan/strset/test/run-deep-recursion.c new file mode 100644 index 000000000000..b9dfe1cfc18c --- /dev/null +++ b/ccan/ccan/strset/test/run-deep-recursion.c @@ -0,0 +1,99 @@ +/* Regression test for auditor finding F1 (2026-08-05 audit): + * strset_iterate_() and strset_clear() recurse once per tree level, so + * a deep enough tree (reachable via documented API calls alone) + * overflows any fixed stack. The child builds a maximally deep + * "staircase" critbit chain, shrinks its stack rlimit, then iterates + * and clears. + * + * On the recursive implementation the child dies with SIGSEGV; an + * iterative implementation passes. The verdict is TODO-wrapped while + * the repair (explicit-stack traversal) is undecided. + */ +#include +#include +#include +#include +#include +#include +#include + +/* Measured recursion frames are ~70 bytes at -O0 and ~16 bytes at -O2, + * so 12000 levels need 190-840 KiB of stack: overflows the 128 KiB + * rlimit set below with margin at any optimization level, but fits in + * a default 8 MiB stack if the rlimit change is ineffective. */ +#define DEPTH 12000 + +static bool count_cb(const char *member, void *p) +{ + unsigned int *n = p; + (void)member; + (*n)++; + return true; +} + +/* Returns 0 on success, 1 on wrong results; crashes when broken. */ +static int child_scenario(void) +{ + static struct strset set; + static char *strs[DEPTH]; + struct rlimit rl; + unsigned int n = 0; + int i, j; + + rl.rlim_cur = 128 * 1024; + rl.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_STACK, &rl); + + strset_init(&set); + /* Staircase chain: 8 split positions per byte, so the tree is a + * chain of depth DEPTH-1 using only ~DEPTH^2/16 bytes of keys. */ + for (i = 0; i < DEPTH; i++) { + int len = i / 8 + 1; + strs[i] = malloc(len + 1); + if (!strs[i]) + abort(); + for (j = 0; j < len - 1; j++) + strs[i][j] = '\xff'; + if (i % 8 == 0) + strs[i][len-1] = 0x01; + else + strs[i][len-1] = (char)(unsigned char)(0xff << (i % 8)); + strs[i][len] = '\0'; + if (!strset_add(&set, strs[i])) + abort(); + } + if (strset_empty(&set)) + return 1; + strset_iterate(&set, count_cb, &n); + if (n != DEPTH) + return 1; + strset_clear(&set); + if (!strset_empty(&set)) + return 1; + + for (i = 0; i < DEPTH; i++) + free(strs[i]); + return 0; +} + +int main(void) +{ + pid_t pid; + int status; + + plan_tests(1); + alarm(120); + + pid = fork(); + if (pid == 0) { + int r = child_scenario(); + /* Don't run atexit/flush: parent prints the TAP. */ + _exit(r); + } + if (waitpid(pid, &status, 0) != pid) + abort(); + + ok1(WIFEXITED(status) && WEXITSTATUS(status) == 0); + + return exit_status(); +} diff --git a/ccan/ccan/strset/test/run-iter.c b/ccan/ccan/strset/test/run-iter.c new file mode 100644 index 000000000000..fa49466aa457 --- /dev/null +++ b/ccan/ccan/strset/test/run-iter.c @@ -0,0 +1,124 @@ +/* Tests for strset_iter_first/strset_iter_next: order must match + * strset_iterate (itself checked against sorted order by run-order.c), + * and deep trees must not crash (see audit-findings/strset.md F1). + * Compiled with varying STRSET_NUM_ITER_PARENTS to exercise the + * slow_mode (successor re-descent) path. */ +#include +#include +#include +#include +#include + +/* A staircase of 8 splits per byte gives a chain of depth ~N. */ +#define NDEEP 3000 + +static struct strset set; +static const char *members[NDEEP + 3]; +static unsigned int nmembers; + +static bool grab(const char *member, void *p) +{ + (void)p; + members[nmembers++] = member; + return true; +} + +int main(void) +{ + unsigned int i, n; + const char *m; + struct strset_iter it; + bool order_ok, slow_seen; + + alarm(60); + plan_tests(7); + + /* Empty set. */ + strset_init(&set); + ok1(strset_iter_first(&it, &set) == NULL); + + /* Just the empty string. */ + strset_add(&set, ""); + m = strset_iter_first(&it, &set); + ok1(m != NULL && m[0] == '\0'); + ok1(strset_iter_next(&it, &set, m) == NULL); + strset_clear(&set); + + /* Random-ish set (xorshift), including "" and shared prefixes. */ + strset_init(&set); + { + static char buf[600][16]; + uint64_t r = 0x123456789abcdef0ULL; + strset_add(&set, ""); + for (i = 0; i < 500; i++) { + unsigned int j, len; + r ^= r << 13; r ^= r >> 7; r ^= r << 17; + len = r % 12 + 1; + for (j = 0; j < len; j++) { + r ^= r << 13; r ^= r >> 7; r ^= r << 17; + buf[i][j] = 'a' + (r % 26); + } + buf[i][len] = '\0'; + /* Force prefix collisions. */ + if (i % 3 == 0 && i > 0) + buf[i][0] = buf[i-1][0]; + strset_add(&set, buf[i]); + } + } + + /* Reference order via the existing (recursive) iterate. */ + nmembers = 0; + strset_iterate(&set, grab, NULL); + n = nmembers; + + order_ok = true; + slow_seen = false; + i = 0; + for (m = strset_iter_first(&it, &set); m; m = strset_iter_next(&it, &set, m)) { + if (it.slow_mode) + slow_seen = true; + if (i >= n || strcmp(m, members[i]) != 0) + order_ok = false; + i++; + } + ok1(i == n); + ok1(order_ok); + /* With the default 16 parents this set should not need the + * slow_mode path; with a tiny override it must have been used. */ + if (STRSET_NUM_ITER_PARENTS >= 16) + ok1(!slow_seen); + else + ok1(slow_seen); + strset_clear(&set); + + /* Deep staircase: iterate without stack overflow, in order. */ + strset_init(&set); + { + static char deep[NDEEP][400]; + unsigned int j; + for (i = 0; i < NDEEP; i++) { + unsigned int len = i / 8 + 1; + for (j = 0; j < len - 1; j++) + deep[i][j] = '\xff'; + if (i % 8 == 0) + deep[i][len-1] = 0x01; + else + deep[i][len-1] = (char)(unsigned char)(0xff << (i % 8)); + deep[i][len] = '\0'; + strset_add(&set, deep[i]); + } + } + + n = 0; + order_ok = true; + for (m = strset_iter_first(&it, &set); m; m = strset_iter_next(&it, &set, m)) { + if (n > 0 && strcmp(m, members[0]) <= 0) + order_ok = false; + members[0] = m; + n++; + } + ok1(n == NDEEP && order_ok); + strset_clear(&set); + + return exit_status(); +} diff --git a/ccan/ccan/structeq/structeq.h b/ccan/ccan/structeq/structeq.h index 81799539c51e..b035521914b0 100644 --- a/ccan/ccan/structeq/structeq.h +++ b/ccan/ccan/structeq/structeq.h @@ -18,6 +18,10 @@ * there isn't any, or how many we expect. A negative number means * "up to or equal to that amount of padding", as padding can be * platform dependent. + * + * Note that members which are themselves structures or unions are + * compared with memcmp(), so any *internal* padding they contain can + * cause false negatives, just like top-level padding would. */ #define STRUCTEQ_DEF(sname, padbytes, ...) \ static inline bool CPPMAGIC_GLUE2(sname, _eq)(const struct sname *_a, \ diff --git a/ccan/ccan/take/take.c b/ccan/ccan/take/take.c index 437855a27c75..83890f45e80f 100644 --- a/ccan/ccan/take/take.c +++ b/ccan/ccan/take/take.c @@ -81,6 +81,10 @@ bool taken(const void *p) memmove(&takenarr[i-1], &takenarr[i], (--num_taken - (i - 1))*sizeof(takenarr[0])); + if (labelarr) { + memmove(&labelarr[i-1], &labelarr[i], + (num_taken - (i - 1))*sizeof(labelarr[0])); + } return true; } @@ -114,6 +118,7 @@ const char *taken_any(void) void take_cleanup(void) { max_taken = num_taken = 0; + allocfail = 0; free(takenarr); takenarr = NULL; free(labelarr); diff --git a/ccan/ccan/take/test/run-allocfail-cleanup.c b/ccan/ccan/take/test/run-allocfail-cleanup.c new file mode 100644 index 000000000000..d7e3afd254f4 --- /dev/null +++ b/ccan/ccan/take/test/run-allocfail-cleanup.c @@ -0,0 +1,46 @@ +#include +#include +#include + +static bool fail_realloc; +static void *my_realloc(void *p, size_t len) +{ + if (fail_realloc) + return NULL; + return realloc(p, len); +} +#define realloc my_realloc + +#include +#include +#include + +static void noop_allocfail(const void *p UNNEEDED) +{ +} + +/* Regression test: take_cleanup() is documented to "remove all taken + * pointers from list", but it leaves the allocfail counter behind, so a + * phantom taken NULL survives the cleanup. */ +int main(void) +{ + static int x; + + alarm(10); + plan_tests(4); + + take_allocfail(noop_allocfail); + ok1(take(&x) == &x); + + /* Force the next take's realloc to fail: phantom taken NULL. */ + fail_realloc = true; + ok1(take(&x) == NULL); + + /* take_cleanup() should remove *all* taken state. */ + take_cleanup(); + /* Both fail today: the phantom NULL is still "taken". */ + ok1(!is_taken(NULL)); + ok1(!taken(NULL)); + + return exit_status(); +} diff --git a/ccan/ccan/take/test/run-debug-labels.c b/ccan/ccan/take/test/run-debug-labels.c new file mode 100644 index 000000000000..361c806da9a6 --- /dev/null +++ b/ccan/ccan/take/test/run-debug-labels.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#define CCAN_TAKE_DEBUG 1 +#include +#include +#include + +/* Regression test: taken() must keep labelarr in sync with takenarr. + * After a non-last entry is consumed, taken_any() must still report the + * label of a *still-taken* pointer, not that of a consumed one. */ +int main(void) +{ + int alpha, beta; + const char *l; + int line_alpha, line_beta; + char expect[80]; + + alarm(10); + plan_tests(8); + + /* Control: removing the last entry keeps labels in sync. */ + line_alpha = __LINE__ + 1; + take(&alpha); + take(&beta); + ok1(taken(&beta)); + l = taken_any(); + ok1(l != NULL); + snprintf(expect, sizeof(expect), ":%d:&alpha", line_alpha); + ok1(strstr(l, expect) != NULL); + ok1(taken(&alpha)); + ok1(!taken_any()); + + take_cleanup(); + + /* Removing the first entry must not misalign the labels. */ + take(&alpha); + line_beta = __LINE__ + 1; + take(&beta); + ok1(taken(&alpha)); + l = taken_any(); + ok1(l != NULL); + snprintf(expect, sizeof(expect), ":%d:&beta", line_beta); + /* Fails today: taken_any() reports take(&alpha)'s label instead. */ + ok1(strstr(l, expect) != NULL); + + take_cleanup(); + + return exit_status(); +} diff --git a/ccan/ccan/tal/grab_file/grab_file.c b/ccan/ccan/tal/grab_file/grab_file.c index fcadc8334362..ee34a912af79 100644 --- a/ccan/ccan/tal/grab_file/grab_file.c +++ b/ccan/ccan/tal/grab_file/grab_file.c @@ -17,9 +17,11 @@ static void *grab_fd_internal(const void *ctx, int fd, bool add_nul_term) size = 0; - if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) + if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode) && st.st_size != 0) max = st.st_size; else + /* Non-regular file, or one reporting a zero size despite + * having content (eg. /proc, /sys): guess and grow. */ max = 16384; buffer = tal_arr(ctx, char, max+add_nul_term); diff --git a/ccan/ccan/tal/grab_file/test/run-size0.c b/ccan/ccan/tal/grab_file/test/run-size0.c new file mode 100644 index 000000000000..790ac4991b5e --- /dev/null +++ b/ccan/ccan/tal/grab_file/test/run-size0.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include + +/* /proc (and /sys) files are S_ISREG with st_size == 0, yet have + * content: grab_file must not return an empty buffer for them. */ +int main(void) +{ + char *s; + + plan_tests(2); + if (access("/proc/self/status", R_OK) != 0) + skip(2, "no /proc/self/status"); + else { + s = grab_file_str(NULL, "/proc/self/status"); + ok1(s != NULL); + ok1(s && strlen(s) > 0); + tal_free(s); + } + + tal_cleanup(); + return exit_status(); +} diff --git a/ccan/ccan/tal/link/link.h b/ccan/ccan/tal/link/link.h index 1919253ca496..e5fc2be6f638 100644 --- a/ccan/ccan/tal/link/link.h +++ b/ccan/ccan/tal/link/link.h @@ -11,6 +11,9 @@ * The object will be freed when @newobj is freed or the last tal_link() * is tal_delink'ed. * + * Note: @newobj must not be tal_free()'d or tal_steal()'d while it has + * links: that aborts. Remove all links first. + * * Returns @newobj or NULL (if an allocation fails). * * Example: diff --git a/ccan/ccan/tal/path/path.c b/ccan/ccan/tal/path/path.c index 75894240b49d..941bcd781fa3 100644 --- a/ccan/ccan/tal/path/path.c +++ b/ccan/ccan/tal/path/path.c @@ -321,7 +321,8 @@ char *path_rel(const tal_t *ctx, const char *from, const char *to) break; } - if (!tal_resize(&ret, maxlen *= 2 + 1)) + maxlen = maxlen * 2 + 1; + if (!tal_resize(&ret, maxlen)) goto fail; } @@ -393,8 +394,12 @@ char *path_simplify(const tal_t *ctx, const char *path) j = sep - ret + 1; else j = 0; + continue; } - continue; + /* Symlink or nonexistent: can't safely step + * back, so keep the ".." literally. */ + ret[j-1] = PATH_SEP; + goto copy; } else if (start) { /* /.. => / */ j = 1; @@ -436,20 +441,27 @@ char *path_basename(const tal_t *ctx, const char *path) /* Trailing slashes need to be trimmed. */ if (!sep[1]) { - const char *end; + size_t end, sep_off; - for (end = sep; end != path; end--) - if (*end != PATH_SEP) + for (end = sep - path; end != 0; end--) + if (path[end] != PATH_SEP) break; - /* Find *previous* / */ - for (sep = end; sep >= path && *sep != PATH_SEP; sep--); + /* Find *previous* / ((size_t)-1 if there is none) */ + for (sep_off = end; ; sep_off--) { + if (path[sep_off] == PATH_SEP) + break; + if (sep_off == 0) { + sep_off = (size_t)-1; + break; + } + } /* All /? Just return / */ - if (end == sep) + if (end == sep_off) ret = tal_strdup(ctx, PATH_SEP_STR); else - ret = tal_strndup(ctx, sep+1, end - sep); + ret = tal_strndup(ctx, path + (sep_off + 1), end - sep_off); } else ret = tal_strdup(ctx, sep + 1); diff --git a/ccan/ccan/tal/path/path.h b/ccan/ccan/tal/path/path.h index 2f7f608b2388..586b48e37854 100644 --- a/ccan/ccan/tal/path/path.h +++ b/ccan/ccan/tal/path/path.h @@ -52,6 +52,9 @@ char *path_simplify(const tal_t *ctx, const char *a TAKES); * * If @a is an absolute path, return a copy of it. Otherwise, attach * @a to @base. + * + * @base and @a must not be NULL, except as the taken result of a + * failed take() chain (e.g. path_join(ctx, take(tal_fmt(...)), "x")). */ char *path_join(const tal_t *ctx, const char *base TAKES, const char *a TAKES); diff --git a/ccan/ccan/tal/path/test/run-simplify.c b/ccan/ccan/tal/path/test/run-simplify.c index 9591132dcf81..6b3570103412 100644 --- a/ccan/ccan/tal/path/test/run-simplify.c +++ b/ccan/ccan/tal/path/test/run-simplify.c @@ -6,7 +6,7 @@ int main(void) { char cwd[1024], *path, *ctx = tal_strdup(NULL, "ctx"); - plan_tests(85); + plan_tests(95); if (!getcwd(cwd, sizeof(cwd))) abort(); @@ -227,6 +227,33 @@ int main(void) ok1(tal_parent(path) == ctx); tal_free(path); + /* Don't trace back over a symlink: keep ".." literally. */ + path = path_simplify(ctx, "run-simplify-link/.."); + ok1(streq(path, "run-simplify-link/..")); + ok1(tal_parent(path) == ctx); + tal_free(path); + + path = path_simplify(ctx, "run-simplify-link/../"); + ok1(streq(path, "run-simplify-link/..")); + ok1(tal_parent(path) == ctx); + tal_free(path); + + path = path_simplify(ctx, "run-simplify-link/../x"); + ok1(streq(path, "run-simplify-link/../x")); + ok1(tal_parent(path) == ctx); + tal_free(path); + + path = path_simplify(ctx, "run-simplify-link/../../foo"); + ok1(streq(path, "run-simplify-link/../../foo")); + ok1(tal_parent(path) == ctx); + tal_free(path); + + /* Nonexistent path: can't prove it's a real dir, keep "..". */ + path = path_simplify(ctx, "run-simplify-nosuch/.."); + ok1(streq(path, "run-simplify-nosuch/..")); + ok1(tal_parent(path) == ctx); + tal_free(path); + /* take tests */ path = path_simplify(ctx, take(tal_strdup(ctx, "/tmp/../tmp/."))); ok1(streq(path, "/tmp")); diff --git a/ccan/ccan/tal/str/str.c b/ccan/ccan/tal/str/str.c index 617b942cd8d0..67bddab4b831 100644 --- a/ccan/ccan/tal/str/str.c +++ b/ccan/ccan/tal/str/str.c @@ -150,8 +150,11 @@ char **tal_strsplit_(const tal_t *ctx, if (flags == STR_EMPTY_OK && dlen) dlen = 1; str += len + dlen; - if (++num == max && !tal_resize(&parts, max*=2 + 1)) - goto fail; + if (++num == max) { + max = max * 2 + 1; + if (!tal_resize(&parts, max)) + goto fail; + } } parts[num] = NULL; @@ -214,33 +217,9 @@ char *tal_strjoin_(const tal_t *ctx, goto out; } -static size_t count_open_braces(const char *string) -{ -#if 1 - size_t num = 0, esc = 0; - - while (*string) { - if (*string == '\\') - esc++; - else { - /* An odd number of \ means it's escaped. */ - if (*string == '(' && (esc & 1) == 0) - num++; - esc = 0; - } - string++; - } - return num; -#else - return strcount(string, "("); -#endif -} - bool tal_strreg_(const tal_t *ctx, const char *string, const char *label, const char *regex, ...) { - size_t nmatch = 1 + count_open_braces(regex); - regmatch_t matches[nmatch]; regex_t r; bool ret = false; unsigned int i; @@ -249,33 +228,39 @@ bool tal_strreg_(const tal_t *ctx, const char *string, const char *label, if (regcomp(&r, regex, REG_EXTENDED) != 0) goto fail_no_re; - if (regexec(&r, string, nmatch, matches, 0) != 0) - goto fail; - - ret = true; - va_start(ap, regex); - for (i = 1; i < nmatch; i++) { - char **arg = va_arg(ap, char **); - if (arg) { - /* eg. ([a-z])? can give "no match". */ - if (matches[i].rm_so == -1) - *arg = NULL; - else { - *arg = tal_strndup_(ctx, - string + matches[i].rm_so, - matches[i].rm_eo - - matches[i].rm_so, - label); - /* FIXME: If we fail, we set some and leak! */ - if (!*arg) { - ret = false; - break; + { + /* re_nsub counts real capture groups: unlike scanning the + * regex text, it is not fooled by '(' inside bracket + * expressions. */ + size_t nmatch = 1 + r.re_nsub; + regmatch_t matches[nmatch]; + + if (regexec(&r, string, nmatch, matches, 0) == 0) { + ret = true; + va_start(ap, regex); + for (i = 1; i < nmatch; i++) { + char **arg = va_arg(ap, char **); + if (arg) { + /* eg. ([a-z])? can give "no match". */ + if (matches[i].rm_so == -1) + *arg = NULL; + else { + *arg = tal_strndup_(ctx, + string + matches[i].rm_so, + matches[i].rm_eo + - matches[i].rm_so, + label); + /* FIXME: If we fail, we set some and leak! */ + if (!*arg) { + ret = false; + break; + } + } } } + va_end(ap); } } - va_end(ap); -fail: regfree(&r); fail_no_re: if (taken(regex)) diff --git a/ccan/ccan/tal/str/test/run-strreg-bracket.c b/ccan/ccan/tal/str/test/run-strreg-bracket.c new file mode 100644 index 000000000000..5495840ef5ed --- /dev/null +++ b/ccan/ccan/tal/str/test/run-strreg-bracket.c @@ -0,0 +1,29 @@ +#include +#include +#include + +/* A '(' inside a bracket expression is not a capture group: the caller + * passes no char** arguments, and tal_strreg_() must not read any. */ +int main(void) +{ + char *m1, *m2; + + plan_tests(6); + + ok1(tal_strreg(NULL, "(", "[(]") == true); + ok1(tal_strreg(NULL, "x", "[(]") == false); + + /* Bracket expression plus real groups. */ + m1 = m2 = NULL; + ok1(tal_strreg(NULL, "(x", "[(]([a-z])(z)?", &m1, &m2) == true); + ok1(m1 && streq(m1, "x")); + ok1(m2 == NULL); + tal_free(m1); + tal_free(m2); + + /* Escaped paren is not a group either. */ + ok1(tal_strreg(NULL, "(", "\\(") == true); + + tal_cleanup(); + return exit_status(); +} diff --git a/ccan/ccan/tal/tal.c b/ccan/ccan/tal/tal.c index 39eb21537f4f..53358fda8265 100644 --- a/ccan/ccan/tal/tal.c +++ b/ccan/ccan/tal/tal.c @@ -244,6 +244,9 @@ static void notify(const struct tal_hdr *ctx, EXTRA_ARG(n)); else cb.destroy(from_tal_hdr(ctx)); + /* Restore: object may have been rescued by a + * tal_steal() from inside the destructor. */ + n->u = cb; } else n->u.notifyfn(from_tal_hdr_or_null(ctx), type, (void *)info); @@ -420,22 +423,26 @@ static bool add_child(struct tal_hdr *parent, struct tal_hdr *child) return true; } -static void del_tree(struct tal_hdr *t, const tal_t *orig, int saved_errno) +static void del_tree(struct tal_hdr *t, const tal_t *orig, int saved_errno); + +/* Free t's children, properties and t itself. The destroying bit is + * already set (by del_tree() or tal_free()). */ +static void del_tree_inner(struct tal_hdr *t, const tal_t *orig, + int saved_errno) { struct prop_hdr *prop; char *ptr, *next; assert(!taken(from_tal_hdr(t))); - /* Already being destroyed? Don't loop. */ - if (unlikely(get_destroying_bit(t->parent_child))) - return; - - set_destroying_bit(&t->parent_child); - /* Call free notifiers. */ notify(t, TAL_NOTIFY_FREE, (tal_t *)orig, saved_errno); + /* A destructor/notifier can rescue the object by tal_steal()ing + * it elsewhere: add_child() clears the destroying bit. */ + if (!get_destroying_bit(t->parent_child)) + return; + /* Now free children and groups. */ prop = find_property(t, CHILDREN); if (prop) { @@ -456,6 +463,16 @@ static void del_tree(struct tal_hdr *t, const tal_t *orig, int saved_errno) freefn(t); } +static void del_tree(struct tal_hdr *t, const tal_t *orig, int saved_errno) +{ + /* Already being destroyed? Don't loop. */ + if (unlikely(get_destroying_bit(t->parent_child))) + return; + + set_destroying_bit(&t->parent_child); + del_tree_inner(t, orig, saved_errno); +} + /* Don't have compiler complain we're returning NULL if we promised not to! */ static void *null_alloc_failed(void) { @@ -525,11 +542,21 @@ void *tal_free(const tal_t *ctx) t = debug_tal(to_tal_hdr(ctx)); if (unlikely(get_destroying_bit(t->parent_child))) return NULL; + /* Unlink and mark destroying before notifying the parent: + * a notifier which (recursively) calls tal_free() on ctx + * is then a no-op rather than unbounded recursion. */ + list_del(&t->list); + set_destroying_bit(&t->parent_child); if (notifiers) notify(ignore_destroying_bit(t->parent_child)->parent, TAL_NOTIFY_DEL_CHILD, ctx, saved_errno); - list_del(&t->list); - del_tree(t, ctx, saved_errno); + /* A notifier can rescue ctx by tal_steal()ing it elsewhere: + * add_child() clears the destroying bit. */ + if (!get_destroying_bit(t->parent_child)) { + errno = saved_errno; + return NULL; + } + del_tree_inner(t, ctx, saved_errno); errno = saved_errno; } return NULL; @@ -543,11 +570,28 @@ void *tal_steal_(const tal_t *new_parent, const tal_t *ctx) newpar = debug_tal(to_tal_hdr_or_null(new_parent)); t = debug_tal(to_tal_hdr(ctx)); - /* Unlink it from old parent. */ - list_del(&t->list); + /* Can't steal into a parent which is being destroyed: + * we'd be linked into the list del_tree() is draining, + * and freed (or looped on) anyway. */ + if (unlikely(get_destroying_bit(newpar->parent_child))) + return NULL; + + /* Unlink it from old parent (an object being destroyed + * has already been unlinked: it can only be stolen as a + * rescue from its destructor/notifier). */ + if (!get_destroying_bit(t->parent_child)) + list_del(&t->list); old_parent = ignore_destroying_bit(t->parent_child)->parent; if (unlikely(!add_child(newpar, t))) { + /* No fallback for a rescue from a destructor: + * re-linking into the old parent would silently + * keep the object (its destructor saw NULL), and if + * the old parent is mid-del_tree it would re-enter + * the list being drained. Leave it unlinked and + * destroying; the free proceeds. */ + if (get_destroying_bit(t->parent_child)) + return NULL; /* We can always add to old parent, because it has a * children property already. */ if (!add_child(old_parent, t)) @@ -800,6 +844,12 @@ bool tal_expand_(tal_t **ctxp, const void *src, size_t size, size_t count) old_len = debug_tal(to_tal_hdr(*ctxp))->bytelen; + /* Check for multiplicative overflow */ + if (size && unlikely(count * size / size != count)) { + call_error("dup size overflow"); + goto out; + } + /* Check for additive overflow */ if (old_len + count * size < old_len) { call_error("dup size overflow"); @@ -810,7 +860,9 @@ bool tal_expand_(tal_t **ctxp, const void *src, size_t size, size_t count) assert(src < *ctxp || (char *)src >= (char *)(*ctxp) + old_len); - if (!tal_resize_(ctxp, size, old_len/size + count, false)) + /* Resize by raw length, so excess bytes are preserved and + * tal_count() grows by exactly count. */ + if (!tal_resize_(ctxp, 1, old_len + count * size, false)) goto out; memcpy((char *)*ctxp + old_len, src, count * size); diff --git a/ccan/ccan/tal/tal.h b/ccan/ccan/tal/tal.h index 347a5e8c801a..9363e2db1e6d 100644 --- a/ccan/ccan/tal/tal.h +++ b/ccan/ccan/tal/tal.h @@ -143,7 +143,16 @@ void *tal_free(const tal_t *p); * * This may need to perform an allocation, in which case it may fail; thus * it can return NULL, otherwise returns @ptr. If @ptr is NULL, this function does - * nothing. + * nothing. It also fails (returning NULL) if @ctx is currently being + * destroyed. + * + * A destructor or TAL_NOTIFY_FREE notifier may use this to rescue @ptr + * from destruction by moving it to a new parent; the free is then + * aborted. The rescue is only noticed once every notifier and + * destructor registered for this pass has run, so later ones still + * fire (and all of them fire again when the object is finally + * destroyed). If the rescue fails (allocation failure), the free + * proceeds. */ #if HAVE_STATEMENT_EXPR /* Weird macro avoids gcc's 'warning: value computed is not used'. */ @@ -174,7 +183,8 @@ void *tal_free(const tal_t *p); * * If @function has not been successfully added as a destructor, this returns * false. Note that if we're inside the destructor call itself, this will - * return false. + * return false, and the destructor remains registered: it is restored when + * the call returns, in case the object was rescued from destruction. */ #define tal_del_destructor(ptr, function) \ tal_del_destructor_((ptr), typesafe_cb(void, void *, (function), (ptr))) @@ -206,7 +216,8 @@ void *tal_free(const tal_t *p); * * If @function has not been successfully added as a destructor, this returns * false. Note that if we're inside the destructor call itself, this will - * return false. + * return false, and the destructor remains registered: it is restored when + * the call returns, in case the object was rescued from destruction. */ #define tal_del_destructor(ptr, function) \ tal_del_destructor_((ptr), typesafe_cb(void, void *, (function), (ptr))) @@ -272,6 +283,17 @@ enum tal_notify_type { * not called when this context is tal_free()d: TAL_NOTIFY_FREE is * considered sufficient for that case. * + * For TAL_NOTIFY_ADD_CHILD, the callback must not tal_free() or + * tal_steal() the child: the allocating call will still return it to + * the caller, so this will crash or corrupt. + * + * For TAL_NOTIFY_DEL_CHILD, the child is already unlinked and marked + * destroying: calling tal_free() on it from the callback is a no-op, + * and tal_steal()ing it rescues it from destruction (aborting the + * free). + * + * In all cases, the callback must not tal_free() @ptr itself. + * * TAL_NOTIFY_ADD_NOTIFIER/TAL_NOTIFIER_DEL_NOTIFIER are called when a * notifier is added or removed (not for this notifier): @info is the * callback. This is also called for tal_add_destructor and diff --git a/ccan/ccan/tal/test/run-expand-odd.c b/ccan/ccan/tal/test/run-expand-odd.c new file mode 100644 index 000000000000..b2f184f5dc50 --- /dev/null +++ b/ccan/ccan/tal/test/run-expand-odd.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + +/* Expanding an object whose bytelen is not a multiple of the element + * size must not write past the (rounded) allocation. */ +int main(void) +{ + char *c; + uint16_t *p, v = 0x4141; + + plan_tests(5); + + c = tal_arr(NULL, char, 5); + memset(c, 0, 5); + p = (uint16_t *)c; + ok1(tal_expand(&p, &v, 1)); + /* Exact length: 5 + 2 == 7 bytes, preserving the odd byte. */ + ok1(tal_bytelen(p) == 7); + /* tal_count() grows by exactly count. */ + ok1(tal_count(p) == 3); + ok1(memcmp(p, "\0\0\0\0\0", 5) == 0); + c = (char *)p; + ok1(c[5] == 0x41 && c[6] == 0x41); + + tal_free(c); + tal_cleanup(); + return exit_status(); +} diff --git a/ccan/ccan/tal/test/run-notifier-delchild.c b/ccan/ccan/tal/test/run-notifier-delchild.c new file mode 100644 index 000000000000..d88695aa2b25 --- /dev/null +++ b/ccan/ccan/tal/test/run-notifier-delchild.c @@ -0,0 +1,57 @@ +#include +#include +#include + +static unsigned int del_child_calls, steal_calls; +static bool rescued; +static char *rescue; + +/* Recursive tal_free() of the announced child must be a no-op. */ +static void free_child(char *parent, enum tal_notify_type type, void *info) +{ + (void)parent; + if (type == TAL_NOTIFY_DEL_CHILD) { + del_child_calls++; + tal_free(info); + } +} + +/* tal_steal() of the announced child rescues it. */ +static void steal_child(char *parent, enum tal_notify_type type, void *info) +{ + (void)parent; + if (type == TAL_NOTIFY_DEL_CHILD) { + steal_calls++; + rescued = (tal_steal(rescue, info) == info); + } +} + +int main(void) +{ + char *parent, *child; + + plan_tests(7); + + /* Recursive free from DEL_CHILD notifier: no-op, no recursion. */ + parent = tal(NULL, char); + ok1(tal_add_notifier(parent, TAL_NOTIFY_DEL_CHILD, free_child)); + child = tal(parent, char); + tal_free(child); + ok1(del_child_calls == 1); + /* Parent is intact and childless. */ + ok1(tal_first(parent) == NULL); + ok1(tal_del_notifier(parent, free_child)); + + /* Steal from DEL_CHILD notifier: rescues the child. */ + rescue = tal(NULL, char); + ok1(tal_add_notifier(parent, TAL_NOTIFY_DEL_CHILD, steal_child)); + child = tal(parent, char); + tal_free(child); + ok1(steal_calls == 1); + ok1(rescued && tal_parent(child) == rescue); + + tal_free(rescue); + tal_free(parent); + tal_cleanup(); + return exit_status(); +} diff --git a/ccan/ccan/tal/test/run-steal-destroy.c b/ccan/ccan/tal/test/run-steal-destroy.c new file mode 100644 index 000000000000..76772a24d5fa --- /dev/null +++ b/ccan/ccan/tal/test/run-steal-destroy.c @@ -0,0 +1,54 @@ +#include +#include +#include + +static char *rescue; +static unsigned int destroy_count; +static bool steal_result; + +static void steal_self(char *p) +{ + destroy_count++; + /* Rescue ourselves: succeeds if rescue is alive, fails if it + * is also being destroyed. */ + steal_result = (tal_steal(rescue, p) == p); +} + +int main(void) +{ + char *victim, *parent, *child; + + plan_tests(10); + + /* Rescue to a live parent: object survives tal_free(). */ + rescue = tal(NULL, char); + victim = tal(NULL, char); + destroy_count = 0; + ok1(tal_add_destructor(victim, steal_self)); + tal_free(victim); + ok1(destroy_count == 1); + ok1(steal_result); + ok1(tal_parent(victim) == rescue); + ok1(tal_first(rescue) == victim); + *victim = '1'; /* still valid */ + ok1(*victim == '1'); + + /* Freeing rescue now: destructor fires again, but stealing into + * the dying rescue fails, so victim is freed (no hang, no UAF). */ + tal_free(rescue); + ok1(destroy_count == 2); + ok1(!steal_result); + + /* Direct case: child destructor tries to steal into the parent + * currently being freed. */ + parent = tal(NULL, char); + child = tal(parent, char); + rescue = parent; + destroy_count = 0; + ok1(tal_add_destructor(child, steal_self)); + tal_free(parent); + ok1(destroy_count == 1); + + tal_cleanup(); + return exit_status(); +} diff --git a/ccan/ccan/tal/test/run-steal-rescue-allocfail.c b/ccan/ccan/tal/test/run-steal-rescue-allocfail.c new file mode 100644 index 000000000000..f9b2d363cd84 --- /dev/null +++ b/ccan/ccan/tal/test/run-steal-rescue-allocfail.c @@ -0,0 +1,80 @@ +/* Regression test (2026-08 review of 4bd458cb): if a destructor's + * rescue-steal fails because add_child(newpar) can't allocate, the + * object must still be freed -- not silently re-attached to its old + * parent (which, if the old parent is itself mid-del_tree, re-fires + * the destructor and loops under persistent allocation failure). */ +#include +#include +#include +#include + +static char *newpar; +static bool steal_ok; +static unsigned int destroy_count; + +static void *null_alloc(size_t size) +{ + (void)size; + return NULL; +} + +static void *null_realloc(void *p, size_t size) +{ + (void)p; + (void)size; + return NULL; +} + +static void my_errorfn(const char *msg) +{ + (void)msg; +} + +static void rescue_destructor(char *p) +{ + destroy_count++; + steal_ok = (tal_steal(newpar, p) == p); +} + +int main(void) +{ + char *oldpar, *victim; + + plan_tests(6); + alarm(10); + + /* Case 1: simple rescue under allocation failure. */ + newpar = tal(NULL, char); /* no children property yet */ + oldpar = tal(NULL, char); + victim = tal(oldpar, char); + ok1(tal_add_destructor(victim, rescue_destructor)); + + destroy_count = 0; + tal_set_backend(null_alloc, null_realloc, free, my_errorfn); + tal_free(victim); + tal_set_backend(malloc, realloc, free, my_errorfn); + + ok1(destroy_count == 1); + ok1(!steal_ok); + /* The object was really freed: not re-attached to oldpar. */ + ok1(tal_first(oldpar) == NULL); + tal_free(oldpar); + tal_free(newpar); + + /* Case 2: old parent itself being destroyed (must not loop). */ + newpar = tal(NULL, char); + oldpar = tal(NULL, char); + victim = tal(oldpar, char); + ok1(tal_add_destructor(victim, rescue_destructor)); + + destroy_count = 0; + tal_set_backend(null_alloc, null_realloc, free, my_errorfn); + tal_free(oldpar); + tal_set_backend(malloc, realloc, free, my_errorfn); + + ok1(destroy_count == 1); + tal_free(newpar); + + tal_cleanup(); + return exit_status(); +} diff --git a/ccan/ccan/tcon/tcon.h b/ccan/ccan/tcon/tcon.h index 35d83e199b8b..eae7c942637c 100644 --- a/ccan/ccan/tcon/tcon.h +++ b/ccan/ccan/tcon/tcon.h @@ -127,6 +127,10 @@ * * It evaluates to @x so you can chain it. * + * Note that a @expr of type void * silently passes against any + * canary (the comparison is legal C); the same applies to + * tcon_check_ptr(). + * * Example: * #define tlist_add(h, n, member) \ * list_add(&tcon_check((h), canary, (n))->raw, &(n)->member) diff --git a/ccan/ccan/time/test/run-divide-roundup.c b/ccan/ccan/time/test/run-divide-roundup.c new file mode 100644 index 000000000000..5deb03ebd963 --- /dev/null +++ b/ccan/ccan/time/test/run-divide-roundup.c @@ -0,0 +1,43 @@ +/* Auditor-added regression test (temporary) for the time_divide() + * double-path roundup defect: for divisors > 2^30 with + * tv_sec % div == div-1 and tv_nsec == 999999999, the true quotient's + * sub-second part is 1e9 - 1/div, which the double computation rounds + * UP to exactly 1000000000, returning a malformed timerel + * (tv_nsec == 1000000000) from well-formed inputs. In DEBUG builds + * time_divide() aborts on its own result (TIMEREL_CHECK at time.c:65). + * + * Currently FAILS (test 1 and 2): r.ts.tv_nsec == 1000000000. + * After repair it must pass: result well-formed and equal to either + * {0, 999999999} (truncation) or {1, 0} (correct round-up). + */ +#include +#include +#include +#include + +int main(void) +{ + struct timerel t, r, trunc = { { 0, 999999999 } }, one = { { 1, 0 } }; + + alarm(10); + plan_tests(3); + + t.ts.tv_sec = 1073741824; /* == div - 1 */ + t.ts.tv_nsec = 999999999; + r = time_divide(t, 1073741825); /* > 2^30: takes the double path */ + + /* Result must be well-formed. */ + ok1(r.ts.tv_sec >= 0 + && r.ts.tv_nsec >= 0 && r.ts.tv_nsec < 1000000000); + /* True value is 0.99999999907s: trunc or rounded-up {1,0} only. */ + ok1(timerel_eq(r, trunc) || timerel_eq(r, one)); + + /* A second point in the same family. */ + t.ts.tv_sec = 2147483648; /* == div - 1 */ + t.ts.tv_nsec = 999999999; + r = time_divide(t, 2147483649); + ok1(r.ts.tv_sec >= 0 + && r.ts.tv_nsec >= 0 && r.ts.tv_nsec < 1000000000); + + return exit_status(); +} diff --git a/ccan/ccan/time/test/run-multiply-overflow.c b/ccan/ccan/time/test/run-multiply-overflow.c new file mode 100644 index 000000000000..5b5d74726845 --- /dev/null +++ b/ccan/ccan/time/test/run-multiply-overflow.c @@ -0,0 +1,35 @@ +/* Auditor-added regression test (temporary) for the time_multiply() + * double-path UB: when t.ts.tv_nsec * mult exceeds ~9.2e27, the + * intermediate `nsec / 1000000000.0` exceeds INT64_MAX and the + * double->time_t conversion at time.c:77 is undefined behavior + * (clang UBSan: "outside the range of representable values of type + * 'long'"). The product genuinely does not fit in a timerel, so this + * is an inferred-precondition gray zone (LIKLEY, not CONFIRMED): the + * test passes in plain builds and only aborts under UBSan. After a + * repair (document the overflow precondition and/or clamp), it must + * run UBSan-clean. + */ +#include +#include +#include +#include +#include + +int main(void) +{ + struct timerel t, r; + + alarm(10); + plan_tests(1); + + t.ts.tv_sec = 0; + t.ts.tv_nsec = 999999999; + /* nsec ~= 1.8e28; nsec/1e9 ~= 1.8e19 > INT64_MAX: UB conversion. */ + r = time_multiply(t, UINT64_MAX); + + /* Any result at all is acceptable in a plain build; the point of + * the test is reaching here without a sanitizer abort. */ + ok1(r.ts.tv_sec != 0 || r.ts.tv_nsec != 0 || true); + + return exit_status(); +} diff --git a/ccan/ccan/time/time.c b/ccan/ccan/time/time.c index 9810792280c7..85eb593102f8 100644 --- a/ccan/ccan/time/time.c +++ b/ccan/ccan/time/time.c @@ -2,6 +2,17 @@ #include #include #include +#include + +/* Largest representable tv_sec (time_t is signed). */ +#define TIMEREL_SEC_MAX \ + ((time_t)((~(uint64_t)0) >> (64 - sizeof(time_t)*CHAR_BIT + 1))) + +static struct timerel timerel_max(void) +{ + struct timerel max = { { TIMEREL_SEC_MAX, 999999999 } }; + return max; +} #if !HAVE_CLOCK_GETTIME #include @@ -58,6 +69,11 @@ struct timerel time_divide(struct timerel t, unsigned long div) /* FIXME: fp is cheating! */ double nsec = rem * 1000000000.0 + t.ts.tv_nsec; res.ts.tv_nsec = nsec / div; + /* Rounding can give exactly 1e9; renormalize. */ + if (res.ts.tv_nsec >= 1000000000) { + res.ts.tv_nsec -= 1000000000; + res.ts.tv_sec++; + } } else { ns = rem * 1000000000 + t.ts.tv_nsec; res.ts.tv_nsec = ns / div; @@ -69,11 +85,16 @@ struct timerel time_multiply(struct timerel t, unsigned long mult) { struct timerel res; + (void)TIMEREL_CHECK(t); + /* Are we going to overflow if we multiply nsec? */ if (mult & ~((1UL << 30) - 1)) { /* FIXME: fp is cheating! */ double nsec = (double)t.ts.tv_nsec * mult; + /* Saturate rather than overflow time_t. */ + if (nsec >= (double)TIMEREL_SEC_MAX * 1000000000.0) + return timerel_max(); res.ts.tv_sec = nsec / 1000000000.0; res.ts.tv_nsec = nsec - (res.ts.tv_sec * 1000000000.0); } else { @@ -82,7 +103,12 @@ struct timerel time_multiply(struct timerel t, unsigned long mult) res.ts.tv_nsec = nsec % 1000000000; res.ts.tv_sec = nsec / 1000000000; } - res.ts.tv_sec += TIMEREL_CHECK(t).ts.tv_sec * mult; + + /* The seconds multiply can overflow too: saturate. */ + if (mult != 0 + && t.ts.tv_sec > (time_t)((TIMEREL_SEC_MAX - res.ts.tv_sec) / mult)) + return timerel_max(); + res.ts.tv_sec += t.ts.tv_sec * mult; return TIMEREL_CHECK(res); } diff --git a/ccan/ccan/time/time.h b/ccan/ccan/time/time.h index cbfeefa055c0..d327e92d80b4 100644 --- a/ccan/ccan/time/time.h +++ b/ccan/ccan/time/time.h @@ -569,6 +569,9 @@ struct timerel time_divide(struct timerel t, unsigned long div); * @t: a relative time. * @mult: number to multiply it by. * + * If the result would not fit in a timerel, the maximum representable + * time is returned. + * * Example: * ... * printf("Time to do 100000 forks would be %u sec\n", diff --git a/ccan/ccan/timer/test/run-expire-alloc-fail.c b/ccan/ccan/timer/test/run-expire-alloc-fail.c new file mode 100644 index 000000000000..62108e298536 --- /dev/null +++ b/ccan/ccan/timer/test/run-expire-alloc-fail.c @@ -0,0 +1,64 @@ +/* Regression test for audit finding F2 (2026-08-05, temporary): + * allocator failure in add_level() during timers_expire() leaves + * timers->level[0] == NULL; timers_expire() then dereferences it at + * timer.c:368 (list_pop(&timers->level[0]->list[off], ...)) -> SEGV. + * + * Expected post-fix behavior encoded here: with allocation failing, + * timers_expire() must not crash and must not lose the timer (it stays + * pending on the far list); once allocation succeeds again the timer + * expires normally. + * + * Currently crashes (NULL dereference) before test 2. */ +#include +#include +/* Include the C files directly. */ +#include +#include + +static bool alloc_fails; + +static void *test_alloc(struct timers *timers, size_t len) +{ + (void)timers; + if (alloc_fails) + return NULL; + return malloc(len); +} + +static void test_free(struct timers *timers, void *p) +{ + (void)timers; + free(p); +} + +int main(void) +{ + struct timers timers; + struct timer t; + const struct timemono epoch = { { 0, 0 } }; + + alarm(10); + plan_tests(5); + + timers_set_allocator(test_alloc, test_free); + timers_init(&timers, epoch); + timer_init(&t); + + alloc_fails = true; + timer_addmono(&timers, &t, grains_to_time(5)); + ok1(timers_check(&timers, NULL)); + + /* Level 0 cannot be allocated: must return gracefully. */ + ok1(timers_expire(&timers, grains_to_time(5)) == NULL); + ok1(timers_check(&timers, NULL)); + + /* Once allocation succeeds, the timer must still expire. */ + alloc_fails = false; + ok1(timers_expire(&timers, grains_to_time(5)) == &t); + ok1(timers_check(&timers, NULL)); + + timers_cleanup(&timers); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/timer/test/run-far-level12.c b/ccan/ccan/timer/test/run-far-level12.c new file mode 100644 index 000000000000..2e95a45e471e --- /dev/null +++ b/ccan/ccan/timer/test/run-far-level12.c @@ -0,0 +1,54 @@ +/* Regression test for audit finding F1 (2026-08-05, temporary): + * a timer >= 2^60 grains in the future lands on level 12 (the top + * level); fast-forwarding across the 2^60 boundary computes + * 1ULL << ((12+1)*TIMER_LEVEL_BITS) == 1ULL << 65 (undefined behavior) + * in timer_fast_forward() (timer.c:317) and add_level() (timer.c:173). + * In practice (x86 shift-count masking) the far timer is never + * promoted, and timers_expire() spins forever when base was 0. + * + * 2^60 grains == ~36.6 years at TIMER_GRANULARITY=1 (ns), ~36,600 + * years at the default 1000 (us); the values are legal per the + * documented API (no upper bound on timemono arguments). + * + * Currently fails: UBSan reports the oversized shift, then the test + * hangs in timers_expire() until alarm() fires. Note: any + * intermediate expire() call would advance base and mask the hang + * (the level-11 far-pull bound then reaches the timer), so the jump + * here is done in one step. */ +#include +#include +/* Include the C files directly. */ +#include +#include + +int main(void) +{ + struct timers timers; + struct timer t; + struct timemono earliest; + const struct timemono epoch = { { 0, 0 } }; + /* level_of(): ilog64((2^60)/2) / TIMER_LEVEL_BITS == 60/5 == 12 */ + const uint64_t when = 1ULL << 60; + + alarm(10); + plan_tests(6); + + timers_init(&timers, epoch); + timer_init(&t); + timer_addmono(&timers, &t, grains_to_time(when)); + ok1(timers_check(&timers, NULL)); + ok1(timer_earliest(&timers, &earliest)); + ok1(timemono_eq(earliest, grains_to_time(when))); + + /* Not due at the epoch (expire == base does not advance base). */ + ok1(!timers_expire(&timers, epoch)); + + /* Must expire when its time comes (UB shift + hang before fix). */ + ok1(timers_expire(&timers, grains_to_time(when)) == &t); + ok1(timers_check(&timers, NULL)); + + timers_cleanup(&timers); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/timer/timer.c b/ccan/ccan/timer/timer.c index ef6b27742679..20263ad80cc1 100644 --- a/ccan/ccan/timer/timer.c +++ b/ccan/ccan/timer/timer.c @@ -153,6 +153,15 @@ static void timers_far_get(struct timers *timers, } } +/* Maximum time covered by levels 0..@level (from base); saturates + * once the range exceeds the representable 64 bits. */ +static uint64_t level_max_time(const struct timers *timers, unsigned int level) +{ + if ((level + 1) * TIMER_LEVEL_BITS >= 64) + return -1ULL; + return timers->base + (1ULL << ((level+1)*TIMER_LEVEL_BITS)) - 1; +} + static void add_level(struct timers *timers, unsigned int level) { struct timer_level *l; @@ -169,8 +178,7 @@ static void add_level(struct timers *timers, unsigned int level) timers->level[level] = l; list_head_init(&from_far); - timers_far_get(timers, &from_far, - timers->base + (1ULL << ((level+1)*TIMER_LEVEL_BITS)) - 1); + timers_far_get(timers, &from_far, level_max_time(timers, level)); while ((t = list_pop(&from_far, struct timer, list)) != NULL) timer_add_raw(timers, t); @@ -313,8 +321,7 @@ static void timer_fast_forward(struct timers *timers, uint64_t time) if (!timers->level[level]) { /* We need any which belong on this level. */ timers_far_get(timers, &list, - timers->base - + (1ULL << ((level+1)*TIMER_LEVEL_BITS))-1); + level_max_time(timers, level)); need_level = level; } else { unsigned src; @@ -353,6 +360,9 @@ struct timer *timers_expire(struct timers *timers, struct timemono expire) if (list_empty(&timers->far)) return NULL; add_level(timers, 0); + /* Allocation failure: timers wait safely on the far list. */ + if (!timers->level[0]) + return NULL; } do { @@ -448,8 +458,13 @@ struct timers *timers_check(const struct timers *timers, const char *abortstr) } past_levels: - base = (timers->base & ~((1ULL << (TIMER_LEVEL_BITS * l)) - 1)) - + (1ULL << (TIMER_LEVEL_BITS * l)) - 1; + if (TIMER_LEVEL_BITS * l < 64) { + base = (timers->base & ~((1ULL << (TIMER_LEVEL_BITS * l)) - 1)) + + (1ULL << (TIMER_LEVEL_BITS * l)) - 1; + } else { + /* Levels cover all representable times: far must be empty. */ + base = -1ULL; + } if (!timer_list_check(&timers->far, base, -1ULL, timers->firsts[ARRAY_SIZE(timers->level)], abortstr)) diff --git a/ccan/ccan/typesafe_cb/test/compile_fail-typesafe_cb-int.c b/ccan/ccan/typesafe_cb/test/compile_fail-typesafe_cb-int.c index c9d47c50b17f..ed3230d8a212 100644 --- a/ccan/ccan/typesafe_cb/test/compile_fail-typesafe_cb-int.c +++ b/ccan/ccan/typesafe_cb/test/compile_fail-typesafe_cb-int.c @@ -10,7 +10,7 @@ void _callback(void (*fn)(void *arg), void *arg) /* Callback is set up to warn if arg isn't a pointer (since it won't * pass cleanly to _callback's second arg. */ #define callback(fn, arg) \ - _callback(typesafe_cb(void, (fn), (arg)), (arg)) + _callback(typesafe_cb(void, void *, (fn), (arg)), (arg)) void my_callback(int something); void my_callback(int something) @@ -23,6 +23,9 @@ int main(void) #ifdef FAIL /* This fails due to arg, not due to cast. */ callback(my_callback, 100); +#if !HAVE_TYPEOF||!HAVE_BUILTIN_CHOOSE_EXPR||!HAVE_BUILTIN_TYPES_COMPATIBLE_P +#error "Unfortunately we don't fail if typesafe_cb is a noop." +#endif #endif return 0; } diff --git a/ccan/ccan/typesafe_cb/test/compile_ok-typesafe_cb-unprototyped.c b/ccan/ccan/typesafe_cb/test/compile_ok-typesafe_cb-unprototyped.c new file mode 100644 index 000000000000..9da7db5db283 --- /dev/null +++ b/ccan/ccan/typesafe_cb/test/compile_ok-typesafe_cb-unprototyped.c @@ -0,0 +1,31 @@ +/* An unprototyped callback defeats typesafe_cb()'s check entirely: + * void (*)() is not compatible with void (*)(int *) in the + * __builtin_types_compatible_p sense, so the macro declines to cast, + * and the conversion to void (*)(void *) is silently accepted (gcc and + * clang give no diagnostic at -Wall). This compile_ok test pins that + * behavior (see audit-findings/typesafe_cb.md F2): callbacks must have + * full prototypes to be checked. */ +#include + +static void _register_callback(void (*cb)(void *arg), void *arg) +{ + (void)cb; + (void)arg; +} + +#define register_callback(cb, arg) \ + _register_callback(typesafe_cb(void, void *, (cb), (arg)), (arg)) + +static void my_callback() +{ +} + +int main(void) +{ + int *p = (void *)0; + + /* With a prototype (eg. void my_callback(char *)), this would + * warn: the argument type does not match. */ + register_callback(my_callback, p); + return 0; +} diff --git a/ccan/ccan/typesafe_cb/typesafe_cb.h b/ccan/ccan/typesafe_cb/typesafe_cb.h index 126d325c7821..9bc76542b14f 100644 --- a/ccan/ccan/typesafe_cb/typesafe_cb.h +++ b/ccan/ccan/typesafe_cb/typesafe_cb.h @@ -78,6 +78,11 @@ * It is assumed that @arg is of pointer type: usually @arg is passed * or assigned to a void * elsewhere anyway. * + * Note that the callback must have a prototype: an unprototyped + * function (ie. "void fn()") defeats this check entirely, and is + * silently accepted with any @arg type (gcc and clang give no + * diagnostic at -Wall). + * * Example: * void _register_callback(void (*fn)(void *arg), void *arg); * #define register_callback(fn, arg) \ diff --git a/ccan/tools/configurator/configurator.c b/ccan/tools/configurator/configurator.c index 085034fd0eec..7beecd494ebe 100644 --- a/ccan/tools/configurator/configurator.c +++ b/ccan/tools/configurator/configurator.c @@ -396,6 +396,9 @@ static const struct test base_tests[] = { { "HAVE_STATEMENT_EXPR", "statement expression support", "INSIDE_MAIN", NULL, NULL, "return ({ int x = argc; x == argc ? 0 : 1; });" }, + { "HAVE_STATIC_ASSERT", "_Static_assert support", + "INSIDE_MAIN", NULL, NULL, + "_Static_assert(1, \"OK\"); return 0;" }, { "HAVE_SYS_FILIO_H", "", "OUTSIDE_MAIN", NULL, NULL, /* Solaris needs this for FIONREAD */ "#include \n" }, From 3ffd6df5c50fa48a8ac052e22a456170b3062dd6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 21 Aug 2026 10:49:04 +0930 Subject: [PATCH 2/2] CCAN: update after 32-bit and MacOS cleanups. The addition of GitHub CI to CCAN, especially 32 bit builds and MacOS tests lead to more refinement (mostly in tests!), so this catches up to that. Signed-off-by: Rusty Russell --- ccan/README | 2 +- ccan/ccan/breakpoint/_info | 1 + ccan/ccan/breakpoint/test/run-blocked.c | 1 + ccan/ccan/breakpoint/test/run-fork.c | 1 + ccan/ccan/breakpoint/test/run-threads.c | 10 ++++ ccan/ccan/cppmagic/test/run-map-empty-arg.c | 1 + ccan/ccan/fdpass/fdpass.c | 21 +++++-- ccan/ccan/fdpass/test/run-multifd-leak.c | 20 +++---- .../htable/test/run-init-sized-alloc-fail.c | 1 + ccan/ccan/htable/test/run-init-sized-huge.c | 1 + ccan/ccan/io/io.c | 8 +++ ccan/ccan/io/io.h | 2 +- ccan/ccan/io/poll.c | 49 ++++++++++++++- ccan/ccan/io/test/run-15-timeout.c | 29 ++++++--- .../test/run-22-POLLHUP-on-listening-socket.c | 29 +++++++-- ccan/ccan/io/test/run-47-exclusive-duplex.c | 36 ++++++++++- .../io/test/run-48-exclusive-duplex-write.c | 59 ++++++++++++++++++- .../io/test/run-50-listener-blocking-accept.c | 10 +++- ccan/ccan/likely/test/run-stats-empty-debug.c | 1 + ccan/ccan/mem/mem.c | 13 ++++ ccan/ccan/mem/mem.h | 9 +++ ccan/ccan/order/test/api.c | 12 ++++ ccan/ccan/pipecmd/test/run-eintr.c | 1 + ccan/ccan/pipecmd/test/run-execfail-fdleak.c | 23 +++++++- ccan/ccan/pipecmd/test/run-execfail-flush.c | 1 + ccan/ccan/ptr_valid/test/run-batch-cache.c | 1 + ccan/ccan/ptr_valid/test/run-child-flush.c | 1 + ccan/ccan/ptr_valid/test/run-errno.c | 1 + ccan/ccan/ptr_valid/test/run-realloc-leak.c | 17 ++++-- ccan/ccan/tal/path/path.c | 15 ++++- ccan/ccan/tal/path/test/run-pushd.c | 7 ++- ccan/ccan/tal/path/test/run-simplify.c | 58 ++++++++++-------- ccan/ccan/time/test/run-divide-roundup.c | 1 + ccan/ccan/time/test/run-multiply-overflow.c | 1 + 34 files changed, 372 insertions(+), 71 deletions(-) diff --git a/ccan/README b/ccan/README index a5272fb35445..0c8ca0607d5c 100644 --- a/ccan/README +++ b/ccan/README @@ -1,3 +1,3 @@ CCAN imported from https://github.com/rustyrussell/ccan. -CCAN version: 08af1cab +CCAN version: 17db5e13 diff --git a/ccan/ccan/breakpoint/_info b/ccan/ccan/breakpoint/_info index 272ee25d3366..8e03912d082b 100644 --- a/ccan/ccan/breakpoint/_info +++ b/ccan/ccan/breakpoint/_info @@ -27,6 +27,7 @@ int main(int argc, char *argv[]) if (strcmp(argv[1], "depends") == 0) { printf("ccan/compiler\n"); + printf("ccan/mem\n"); return 0; } diff --git a/ccan/ccan/breakpoint/test/run-blocked.c b/ccan/ccan/breakpoint/test/run-blocked.c index 1d52fb69a126..e46c7a4def9d 100644 --- a/ccan/ccan/breakpoint/test/run-blocked.c +++ b/ccan/ccan/breakpoint/test/run-blocked.c @@ -11,6 +11,7 @@ * Fails against the current code: the second ok1 fails * (breakpoint_under_debug == true) and the process is then killed by * SIGTRAP when the mask is restored. Must pass after repair. */ +#include #include #include #include diff --git a/ccan/ccan/breakpoint/test/run-fork.c b/ccan/ccan/breakpoint/test/run-fork.c index 66a383508205..11f9fa837c1f 100644 --- a/ccan/ccan/breakpoint/test/run-fork.c +++ b/ccan/ccan/breakpoint/test/run-fork.c @@ -12,6 +12,7 @@ * * Fails against the current code (child killed by SIGTRAP). Must pass * after repair. */ +#include #include #include #include diff --git a/ccan/ccan/breakpoint/test/run-threads.c b/ccan/ccan/breakpoint/test/run-threads.c index a765e9b8f437..67709731d9cc 100644 --- a/ccan/ccan/breakpoint/test/run-threads.c +++ b/ccan/ccan/breakpoint/test/run-threads.c @@ -13,8 +13,10 @@ * uninitialized state, simulating many first-use races. Fails against * the current code (a child is killed by SIGTRAP, usually in round 0). * Must pass after repair. */ +#include #include #include +#include #include #include #include @@ -39,6 +41,14 @@ int main(void) { int round, crashed = 0; + /* Deliberately hammers a known, documented, unsynchronized + * race (audit F2); valgrind's own instrumentation of it isn't + * meaningful signal. */ + if (mem_under_valgrind()) { + plan_skip_all("not meaningful under valgrind"); + return exit_status(); + } + alarm(60); plan_tests(1); diff --git a/ccan/ccan/cppmagic/test/run-map-empty-arg.c b/ccan/ccan/cppmagic/test/run-map-empty-arg.c index 16ee82cdeeae..06dc1eac4664 100644 --- a/ccan/ccan/cppmagic/test/run-map-empty-arg.c +++ b/ccan/ccan/cppmagic/test/run-map-empty-arg.c @@ -5,6 +5,7 @@ * cannot distinguish "expands to nothing" from "absent", so this is * now the documented behavior (cppmagic.h CPPMAGIC_MAP doc note). * This test pins it. */ +#include #include "config.h" #include diff --git a/ccan/ccan/fdpass/fdpass.c b/ccan/ccan/fdpass/fdpass.c index c64ccc11ce8a..51b0c1a230f2 100644 --- a/ccan/ccan/fdpass/fdpass.c +++ b/ccan/ccan/fdpass/fdpass.c @@ -50,9 +50,17 @@ int fdpass_recv(int sockin) struct iovec iov; int fd; char c; + /* Only one fd is ever legitimate here, but size the buffer for + * several: some kernels don't cleanly truncate/reject a + * SCM_RIGHTS message that overflows a too-small ancillary + * buffer (observed: a buffer sized for exactly one fd let a + * two-fd message through complete and untruncated, silently + * leaking the second). Generous headroom means any extra fds a + * malformed/malicious message carries stay visible in cmsg data + * below, where the check after recvmsg() closes them all. */ union { /* Ancillary data buffer, wrapped in a union in order to ensure it is suitably aligned */ - char buf[CMSG_SPACE(sizeof(fd))]; + char buf[CMSG_SPACE(16 * sizeof(fd))]; struct cmsghdr align; } u; @@ -79,9 +87,14 @@ int fdpass_recv(int sockin) return -1; } - if (cmsg->cmsg_len != CMSG_LEN(sizeof(fd))) { - /* The kernel already installed any fds the message - * carried; don't leak them. */ + if ((msg.msg_flags & MSG_CTRUNC) || cmsg->cmsg_len != CMSG_LEN(sizeof(fd))) { + /* Either our ancillary buffer was too small to hold what + * the peer sent (MSG_CTRUNC: cmsg_len alone can't be + * trusted to catch this, since on some ABIs the truncated + * size happens to equal CMSG_LEN(sizeof(fd)) even though + * more was sent), or the peer's message didn't carry + * exactly one fd. Either way, the kernel already + * installed any fds it managed to fit; don't leak them. */ if (cmsg->cmsg_len >= CMSG_LEN(0) && cmsg->cmsg_len <= msg.msg_controllen) { int *fds = (int *)CMSG_DATA(cmsg); diff --git a/ccan/ccan/fdpass/test/run-multifd-leak.c b/ccan/ccan/fdpass/test/run-multifd-leak.c index b1d281fde2cf..d809f13d535f 100644 --- a/ccan/ccan/fdpass/test/run-multifd-leak.c +++ b/ccan/ccan/fdpass/test/run-multifd-leak.c @@ -1,13 +1,11 @@ -/* Temporary auditor regression test (audit 2026-08-05). - * Proves: when the peer sends TWO fds in ONE SCM_RIGHTS cmsg - * (cmsg_len = CMSG_LEN(2*sizeof(int)), which fits the receiver's - * CMSG_SPACE(sizeof(int)) control buffer), fdpass_recv rejects the - * message but the kernel has already installed both fds into this - * process; fdpass_recv never closes them -> fd leak (observed: 32 fds - * leaked over 16 rejected recvs). Repeatable by any non-fdpass peer; - * resource-exhaustion DoS. - * Currently FAILS; must pass after repair (reject path must close any - * fds carried by a malformed SCM_RIGHTS cmsg). */ +/* Regression test: when the peer sends TWO fds in ONE SCM_RIGHTS cmsg + * (cmsg_len = CMSG_LEN(2*sizeof(int))), fdpass_recv must reject the + * message without leaking whatever fd(s) the kernel already installed + * into this process. On some ABIs (notably 32-bit, where + * CMSG_SPACE(sizeof(int)) leaves room for only one fd's worth of + * ancillary data) the kernel truncates the message down to what looks + * like a legitimate single-fd receive, so the check can't rely on + * cmsg_len alone and must also honour MSG_CTRUNC. */ #include /* Include the C files directly. */ #include @@ -23,7 +21,7 @@ static int count_fds(void) { - DIR *d = opendir("/proc/self/fd"); + DIR *d = opendir("/dev/fd"); struct dirent *de; int n = 0; diff --git a/ccan/ccan/htable/test/run-init-sized-alloc-fail.c b/ccan/ccan/htable/test/run-init-sized-alloc-fail.c index 176dea0b60ba..568ca62e9552 100644 --- a/ccan/ccan/htable/test/run-init-sized-alloc-fail.c +++ b/ccan/ccan/htable/test/run-init-sized-alloc-fail.c @@ -7,6 +7,7 @@ * indexed up to (1< #include #include #include diff --git a/ccan/ccan/htable/test/run-init-sized-huge.c b/ccan/ccan/htable/test/run-init-sized-huge.c index 06cac1e054c2..8f776ab0d3a6 100644 --- a/ccan/ccan/htable/test/run-init-sized-huge.c +++ b/ccan/ccan/htable/test/run-init-sized-huge.c @@ -10,6 +10,7 @@ * checks the requested size against the overflow-free computation, so it * fails on 32-bit before the fix and passes everywhere after it. */ +#include #include #include #include diff --git a/ccan/ccan/io/io.c b/ccan/ccan/io/io.c index c4ad9cac5898..20e0f1550b78 100644 --- a/ccan/ccan/io/io.c +++ b/ccan/ccan/io/io.c @@ -414,6 +414,14 @@ static enum plan_result do_plan(struct io_conn *conn, struct io_plan *plan, return EXHAUSTED; if (errno == EPIPE && idle_on_epipe) { plan->status = IO_UNSET; + /* Clear any exclusivity on this (now-idle) plan: + * otherwise it stays counted, permanently + * suppressing polling on the *other* direction too + * (see exclude_pollfds()), even though nothing will + * ever clear it since this plan isn't active to be + * un-exclusived through the normal io_conn_*_exclusive() + * call the caller would otherwise make. */ + backend_set_exclusive(plan, false); backend_new_plan(conn); return UNINTERESTED; } diff --git a/ccan/ccan/io/io.h b/ccan/ccan/io/io.h index 5d084828b96a..b7a871c4d3f5 100644 --- a/ccan/ccan/io/io.h +++ b/ccan/ccan/io/io.h @@ -536,7 +536,7 @@ struct io_plan *io_halfclose(struct io_conn *conn); * * Example: * // Silly example to wait then close. - * static struct io_plan *wait(struct io_conn *conn, void *b) + * static struct io_plan *wait_then_close(struct io_conn *conn, void *b) * { * return io_wait(conn, b, io_close_cb, NULL); * } diff --git a/ccan/ccan/io/poll.c b/ccan/ccan/io/poll.c index dd21dbc1e6e3..edf84b75d57d 100644 --- a/ccan/ccan/io/poll.c +++ b/ccan/ccan/io/poll.c @@ -473,7 +473,19 @@ void *io_loop(struct timers *timers, struct timer **expired) if (events & POLLIN) { accept_conn(l); r--; - } else if (events & (POLLHUP|POLLNVAL|POLLERR)) { + } else if (events) { + /* We only ever ask for POLLIN here. + * POLLHUP/POLLNVAL/POLLERR cover + * Linux's hangup-on-shutdown case + * (see run-22-POLLHUP-on-listening- + * socket.c), but as with the + * non-listener case below, macOS's + * exact revents combination for + * socket errors/shutdown is known to + * differ from Linux's; don't risk + * falling through both branches (and + * thus never closing the listener) on + * a combination we didn't predict. */ r--; errno = EBADF; io_close_listener(l); @@ -489,12 +501,43 @@ void *io_loop(struct timers *timers, struct timer **expired) * ECONNREFUSED. */ r--; /* Get fd's specific error to find Mac's - * ECONNREFUSED, among others */ - if(getsockopt(fds[i]->fd, SOL_SOCKET, SO_ERROR, + * ECONNREFUSED, among others. getsockopt() + * doubles as its own error-value buffer here: + * on success &errno receives the socket's + * SO_ERROR; on failure, errno gets set as + * usual by the failed call itself. */ + if (getsockopt(fds[i]->fd, SOL_SOCKET, SO_ERROR, &errno, &errno_len) == -1) { + if (errno == ENOTSOCK) { + /* Not a socket at all (e.g. a + * plain fd or /dev/null added + * via io_new_conn()): there's + * no SO_ERROR to learn here. + * Give the connection's own + * read/write a normal chance + * to run and discover the real + * outcome itself (EOF, a real + * error, or nothing), instead + * of inventing a misleading + * EBADF and closing blind. + * + * events here is only the + * error bits we matched this + * branch on (by construction, + * neither POLLIN nor POLLOUT); + * io_ready() only acts on + * those two bits, so pass what + * we actually asked poll() for + * instead, or it's a same-state + * no-op that spins forever. */ + io_ready(c, pollfds[i].events); + goto next_fd; + } errno = EBADF; } io_close(c); + next_fd: + ; } } } diff --git a/ccan/ccan/io/test/run-15-timeout.c b/ccan/ccan/io/test/run-15-timeout.c index 64741c4f0c86..2deeb30d2f4e 100644 --- a/ccan/ccan/io/test/run-15-timeout.c +++ b/ccan/ccan/io/test/run-15-timeout.c @@ -86,9 +86,12 @@ int main(void) int fd, status; /* This is how many tests you plan to run */ - plan_tests(21); + plan_tests(20); d->state = 0; - d->timeout_usec = 100000; + /* Wide margin: on a loaded/throttled CI runner, a tight gap here + * risks the parent's own scheduling delay eating into it before + * the child even gets to check the connection. */ + d->timeout_usec = 300000; timers_init(&d->timers, time_mono()); timer_init(&d->timer); fd = make_listen_fd(PORT, &addrinfo); @@ -108,7 +111,7 @@ int main(void) if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) exit(2); signal(SIGPIPE, SIG_IGN); - usleep(500000); + usleep(2000000); for (i = 0; i < strlen("hellothere"); i++) { if (write(fd, "hellothere" + i, 1) != 1) break; @@ -136,11 +139,21 @@ int main(void) /* It should have died. */ ok1(wait(&status)); ok1(WIFEXITED(status)); - ok1(WEXITSTATUS(status) < sizeof(d->buf)); - - /* This one shouldn't time out. */ + /* Not asserted: how many bytes the child got out before its write + * failed depends on TCP half-close timing, not just wall-clock + * margins -- our close() above only stops us from reading, it + * doesn't forcibly reject writes already in flight from the + * child's side, so some/all of them can legitimately still + * succeed depending on how fast the kernel gets around to it. + * The timeout firing correctly (already checked above: state==1, + * expired==&d->timer) is the actual thing under test. */ + diag("child wrote %d bytes before its write failed (or didn't)", + WEXITSTATUS(status)); + + /* This one shouldn't time out. Same wide-margin reasoning as + * above, mirrored. */ d->state = 0; - d->timeout_usec = 500000; + d->timeout_usec = 2000000; fflush(stdout); if (!fork()) { @@ -154,7 +167,7 @@ int main(void) if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) exit(2); signal(SIGPIPE, SIG_IGN); - usleep(100000); + usleep(300000); for (i = 0; i < strlen("hellothere"); i++) { if (write(fd, "hellothere" + i, 1) != 1) break; diff --git a/ccan/ccan/io/test/run-22-POLLHUP-on-listening-socket.c b/ccan/ccan/io/test/run-22-POLLHUP-on-listening-socket.c index e3b6c4134f4e..931afdb77dfc 100644 --- a/ccan/ccan/io/test/run-22-POLLHUP-on-listening-socket.c +++ b/ccan/ccan/io/test/run-22-POLLHUP-on-listening-socket.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -43,18 +45,37 @@ static int make_listen_fd(const char *port, struct addrinfo **info) int main(void) { struct addrinfo *addrinfo = NULL; + struct io_listener *l; + struct timers timers; + struct timer timer; + struct timer *expired; int fd; /* This is how many tests you plan to run */ plan_tests(1); fd = make_listen_fd(PORT, &addrinfo); freeaddrinfo(addrinfo); - io_new_listener(NULL, fd, io_never, NULL); + l = io_new_listener(NULL, fd, io_never, NULL); - /* Anyone could do this; a child doing it will cause poll to return - * POLLHUP only! */ + /* Anyone could do this; on Linux, a child doing it causes poll() + * to return POLLHUP -- but that's not universal: on macOS, poll() + * doesn't report *any* event for an unconnected listening socket + * that's been shut down, so io_loop() would otherwise never + * return. Bound the wait so this can't hang either way, and + * treat the platforms where the event never arrives as a known, + * documented gap rather than a failure. */ shutdown(fd, SHUT_RDWR); - ok1(io_loop(NULL, NULL) == NULL); + timers_init(&timers, time_mono()); + timer_init(&timer); + timer_addrel(&timers, &timer, time_from_sec(2)); + if (io_loop(&timers, &expired) == NULL && !expired) { + ok1(true); + } else { + skip(1, "poll() did not report the shutdown listening" + " socket on this platform"); + io_close_listener(l); + } + timers_cleanup(&timers); /* This exits depending on whether all tests passed */ return exit_status(); diff --git a/ccan/ccan/io/test/run-47-exclusive-duplex.c b/ccan/ccan/io/test/run-47-exclusive-duplex.c index 60e6de4101cf..c2a8b91f8249 100644 --- a/ccan/ccan/io/test/run-47-exclusive-duplex.c +++ b/ccan/ccan/io/test/run-47-exclusive-duplex.c @@ -46,6 +46,37 @@ static struct io_plan *write_more(struct io_conn *conn, struct data *d) write_done, d); } +/* Read-exclusive suppresses writes for the connection's whole life here + * (unlike write-exclusive's EPIPE-idle case, read's EOF closes the + * conn outright rather than idling, so there's no point where write + * could resume): the pattern should be nothing but "<...>"-marked + * reads, whose concatenated content -- across however many chunks the + * peer's two writes happened to arrive in, which is TCP/kernel + * delivery detail, not something to assert an exact split for -- + * equals what was sent. */ +static bool exclusive_read_pattern_ok(const char *pattern, const char *expect) +{ + const char *p; + char *data; + size_t len = 0; + bool ok; + + for (p = pattern; *p; p++) { + if (*p == '>') + return false; /* a write happened: exclusivity lied */ + } + + data = malloc(strlen(pattern) + 1); + for (p = pattern; *p; p++) { + if (*p != '<') + data[len++] = *p; + } + data[len] = '\0'; + ok = (strcmp(data, expect) == 0); + free(data); + return ok; +} + static struct io_plan *read_priority_init(struct io_conn *conn, struct data *d) { /* This should suppress the write */ @@ -131,8 +162,9 @@ int main(void) d.pattern = tal_arrz(NULL, char, 1); ok1(io_loop(NULL, NULL) == NULL); - /* No trace of writes */ - ok1(strcmp(d.pattern, "<1hellothere<1helloagain") == 0); + /* No trace of writes; all of the peer's data read, however it + * got chunked. */ + ok1(exclusive_read_pattern_ok(d.pattern, "1hellothere1helloagain")); tal_free(d.pattern); ok1(wait(&status)); diff --git a/ccan/ccan/io/test/run-48-exclusive-duplex-write.c b/ccan/ccan/io/test/run-48-exclusive-duplex-write.c index 897f83f7f490..1eaf5cec18f2 100644 --- a/ccan/ccan/io/test/run-48-exclusive-duplex-write.c +++ b/ccan/ccan/io/test/run-48-exclusive-duplex-write.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #define PORT "65048" @@ -46,6 +47,46 @@ static struct io_plan *write_more(struct io_conn *conn, struct data *d) write_done, d); } +/* Once the write side idles, the read side correctly resumes: some + * prefix of pure writes (">"), then whatever the peer wrote arrives in + * "<...>"-marked chunks. Two things vary across platforms, so neither + * can be asserted as one fixed outcome: + * - Exact chunk boundaries depend on TCP/kernel delivery granularity. + * - Whether the write side idles at all before the whole connection + * closes depends on how the kernel reports the peer's close: EPIPE + * (graceful; write idles, buffered peer data is then read) vs. + * ECONNRESET (hard reset; io_close()s the whole conn directly, any + * unread peer data is legitimately lost, same as Linux's TCP + * generally does for this in-flight-data-on-close shape). + * So: writes must never follow a read (exclusivity actually held), and + * whatever *did* get read, concatenated across however many chunks, + * must be a prefix of what was sent -- empty (nothing read: hard + * reset) and the full string (graceful: all of it read) both count. */ +static bool exclusive_write_pattern_ok(const char *pattern, const char *expect) +{ + const char *p; + char *data; + size_t len = 0; + bool ok, seen_read = false; + + for (p = pattern; *p; p++) { + if (*p == '<') + seen_read = true; + else if (*p == '>' && seen_read) + return false; /* write after a read: exclusivity lied */ + } + + data = malloc(strlen(pattern) + 1); + for (p = pattern; *p; p++) { + if (*p != '<' && *p != '>') + data[len++] = *p; + } + data[len] = '\0'; + ok = (strncmp(data, expect, len) == 0); + free(data); + return ok; +} + static struct io_plan *write_priority_init(struct io_conn *conn, struct data *d) { /* This should suppress the read */ @@ -55,9 +96,22 @@ static struct io_plan *write_priority_init(struct io_conn *conn, struct data *d) static struct io_plan *init_conn(struct io_conn *conn, struct data *d) { + int sndbuf = 1024; + /* Free listener so when conns close we exit io_loop */ io_close_listener(d->l); + /* We write 1 byte at a time in an exclusive (uninterruptible) loop + * until the peer's close is noticed -- the peer here never reads, + * so that only happens once the send buffer fills and write() + * returns EAGAIN/EPIPE. Default buffer sizes vary a lot across + * platforms/kernel versions (observed to be large enough on some + * macOS configurations that this loop takes an unpredictably long + * time -- not blocked, just very slow -- to naturally get there). + * Force it small so this is fast and deterministic everywhere. */ + setsockopt(io_conn_fd(conn), SOL_SOCKET, SO_SNDBUF, + &sndbuf, sizeof(sndbuf)); + return io_duplex(conn, read_more(conn, d), write_priority_init(conn, d)); } @@ -131,8 +185,9 @@ int main(void) d.pattern = tal_arrz(NULL, char, 1); ok1(io_loop(NULL, NULL) == NULL); - /* No trace of reads */ - ok1(strspn(d.pattern, ">") == strlen(d.pattern)); + /* Writes only until the write side idles; all of the peer's data + * read after that, however it got chunked. */ + ok1(exclusive_write_pattern_ok(d.pattern, "1hellothere1helloagain")); tal_free(d.pattern); ok1(wait(&status)); diff --git a/ccan/ccan/io/test/run-50-listener-blocking-accept.c b/ccan/ccan/io/test/run-50-listener-blocking-accept.c index 0fcd390be586..df159e68547f 100644 --- a/ccan/ccan/io/test/run-50-listener-blocking-accept.c +++ b/ccan/ccan/io/test/run-50-listener-blocking-accept.c @@ -90,7 +90,11 @@ static int make_listen_fd(const char *port) int main(void) { - struct sockaddr_in sa; + /* make_listen_fd() uses AF_UNSPEC, and getaddrinfo() commonly + * resolves that to IPv6 on macOS (vs. IPv4 here) -- a fixed + * sockaddr_in and a hardcoded AF_INET client socket below would + * mismatch whichever family the listener actually got. */ + struct sockaddr_storage sa; socklen_t salen = sizeof(sa); int flags, status; @@ -110,9 +114,11 @@ int main(void) fflush(stdout); if (fork() == 0) { alarm(5); - client_fd = socket(AF_INET, SOCK_STREAM, 0); if (getsockname(listen_fd, (struct sockaddr *)&sa, &salen) != 0) exit(1); + client_fd = socket(sa.ss_family, SOCK_STREAM, 0); + if (client_fd < 0) + exit(1); if (connect(client_fd, (struct sockaddr *)&sa, salen) != 0) exit(1); io_poll_override(rst_poll); diff --git a/ccan/ccan/likely/test/run-stats-empty-debug.c b/ccan/ccan/likely/test/run-stats-empty-debug.c index 36aab1ed373a..3363bdcc47fe 100644 --- a/ccan/ccan/likely/test/run-stats-empty-debug.c +++ b/ccan/ccan/likely/test/run-stats-empty-debug.c @@ -1,4 +1,5 @@ #define CCAN_LIKELY_DEBUG 1 +#include #include #include #include diff --git a/ccan/ccan/mem/mem.c b/ccan/ccan/mem/mem.c index ddb84c9d108a..e51409a690c8 100644 --- a/ccan/ccan/mem/mem.c +++ b/ccan/ccan/mem/mem.c @@ -3,9 +3,22 @@ #include "config.h" #include +#include #include #include +bool mem_under_valgrind(void) +{ + const char *e = getenv("RUNNING_ON_VALGRIND"); + if (e && strcmp(e, "1") == 0) + return true; +#if HAVE_VALGRIND_MEMCHECK_H + return RUNNING_ON_VALGRIND; +#else + return false; +#endif +} + #if !HAVE_MEMMEM void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) diff --git a/ccan/ccan/mem/mem.h b/ccan/ccan/mem/mem.h index 2c61b92d6fc5..018e32fa7a2b 100644 --- a/ccan/ccan/mem/mem.h +++ b/ccan/ccan/mem/mem.h @@ -258,6 +258,15 @@ static inline void *memcheck_(const void *data, size_t len) } #endif +/** + * mem_under_valgrind - true if we're running under valgrind. + * + * Checks RUNNING_ON_VALGRIND() if available at compile time, and + * RUNNING_ON_VALGRIND=1 in the environment (set by ccanlint) either + * way. + */ +bool mem_under_valgrind(void); + #if HAVE_TYPEOF /** * memcheck - check that a memory region is initialized diff --git a/ccan/ccan/order/test/api.c b/ccan/ccan/order/test/api.c index 61ec96f6bb09..851873fc1d10 100644 --- a/ccan/ccan/order/test/api.c +++ b/ccan/ccan/order/test/api.c @@ -121,11 +121,23 @@ int main(void) TEST_SCALAR(unsigned, uint, 0, 1, 10, INT_MAX, (unsigned)INT_MAX+1, -10, -1); + /* These sequences must be strictly increasing. On platforms + * where long is the same width as int (e.g. 32-bit), INT_MIN == + * LONG_MIN and INT_MAX == LONG_MAX, so including both would + * produce equal-valued (non-strictly-increasing) entries. */ +#if LONG_MAX > INT_MAX TEST_SCALAR(long, long, LONG_MIN, INT_MIN, -10, -1, 0, 1, 10, INT_MAX, LONG_MAX); +#else + TEST_SCALAR(long, long, LONG_MIN, -10, -1, 0, 1, 10, LONG_MAX); +#endif +#if ULONG_MAX > UINT_MAX TEST_SCALAR(unsigned long, ulong, 0, 1, 10, INT_MAX, (unsigned long)INT_MAX+1, LONG_MAX, (unsigned long)LONG_MAX+1, -10, -1); +#else + TEST_SCALAR(unsigned long, ulong, 0, 1, 10, LONG_MAX, -10, -1); +#endif TEST_SCALAR(float, float, -INFINITY, -FLT_MAX, -1.0, 0.0, FLT_MIN, 0.1, M_E, M_PI, 5.79, FLT_MAX, INFINITY); diff --git a/ccan/ccan/pipecmd/test/run-eintr.c b/ccan/ccan/pipecmd/test/run-eintr.c index 3b9efcf4e46f..19426da4bd7e 100644 --- a/ccan/ccan/pipecmd/test/run-eintr.c +++ b/ccan/ccan/pipecmd/test/run-eintr.c @@ -2,6 +2,7 @@ * SA_RESTART interrupts the parent's blocking read(execfail[0]); * pipecmdarr must not then report success for a command which does * not exist. */ +#include #include /* Include the C files directly. */ #include diff --git a/ccan/ccan/pipecmd/test/run-execfail-fdleak.c b/ccan/ccan/pipecmd/test/run-execfail-fdleak.c index 7c2900024456..e9da49c36f14 100644 --- a/ccan/ccan/pipecmd/test/run-execfail-fdleak.c +++ b/ccan/ccan/pipecmd/test/run-execfail-fdleak.c @@ -3,19 +3,21 @@ * (b) pipe()/fcntl()/fork() fails after the command pipes were created. * Both paths only close par_close[] and forget tochild[1], fromchild[0] * and errfromchild[0]. */ +#include #include /* Include the C files directly. */ #include #include #include #include +#include #include #include #include static int count_fds(void) { - DIR *d = opendir("/proc/self/fd"); + DIR *d = opendir("/dev/fd"); struct dirent *de; int n = 0; @@ -70,8 +72,23 @@ int main(void) getrlimit(RLIMIT_NOFILE, &saved); before = count_fds(); rl = saved; - /* Room for exactly the three command pipes (6 fds). */ - rl.rlim_cur = before + 6; + { + /* A count of open fds isn't a safe basis for the limit: + * if our fd table has gaps (e.g. the test harness holds + * some higher-numbered fds open), pipecmdarr()'s opens + * fill those gaps first and the count undercounts how + * many low-numbered slots are actually free. Probe the + * fd number the kernel would hand out next instead, so + * the limit leaves room for exactly the three command + * pipes (6 fds). */ + int next = open("/dev/null", O_RDONLY); + if (next < 0) { + ok(0, "probe open failed"); + exit(1); + } + rl.rlim_cur = next + 6; + close(next); + } if (setrlimit(RLIMIT_NOFILE, &rl) != 0) { ok(0, "setrlimit failed"); exit(1); diff --git a/ccan/ccan/pipecmd/test/run-execfail-flush.c b/ccan/ccan/pipecmd/test/run-execfail-flush.c index 09675a2623b0..4a05bcdd6bee 100644 --- a/ccan/ccan/pipecmd/test/run-execfail-flush.c +++ b/ccan/ccan/pipecmd/test/run-execfail-flush.c @@ -1,6 +1,7 @@ /* Regression test: on exec failure the child calls exit(127) instead of * _exit(127), so it flushes the parent's inherited stdio buffers a second * time (and runs atexit handlers) when an output stream is preserved. */ +#include #include /* Include the C files directly. */ #include diff --git a/ccan/ccan/ptr_valid/test/run-batch-cache.c b/ccan/ccan/ptr_valid/test/run-batch-cache.c index 50c8a750364e..7f6a9b2bdf8a 100644 --- a/ccan/ccan/ptr_valid/test/run-batch-cache.c +++ b/ccan/ccan/ptr_valid/test/run-batch-cache.c @@ -3,6 +3,7 @@ * ptr_valid_batch_write() claim the page is writable (and a cached * write failure makes ptr_valid_batch_read() claim it is unreadable). * Currently fails: not ok 2 and not ok 4. */ +#include #include /* Include the C files directly. */ #include diff --git a/ccan/ccan/ptr_valid/test/run-child-flush.c b/ccan/ccan/ptr_valid/test/run-child-flush.c index 379d4cb9e05d..12507dafc6b1 100644 --- a/ccan/ccan/ptr_valid/test/run-child-flush.c +++ b/ccan/ccan/ptr_valid/test/run-child-flush.c @@ -4,6 +4,7 @@ * a second time. We force the fallback by undefining * HAVE_PROC_SELF_MAPS (same code path as /proc being unavailable). * Currently fails: not ok 2. */ +#include #include #undef HAVE_PROC_SELF_MAPS #define HAVE_PROC_SELF_MAPS 0 diff --git a/ccan/ccan/ptr_valid/test/run-errno.c b/ccan/ccan/ptr_valid/test/run-errno.c index 7cce2e22e913..1c2fd4f4f4cd 100644 --- a/ccan/ccan/ptr_valid/test/run-errno.c +++ b/ccan/ccan/ptr_valid/test/run-errno.c @@ -1,6 +1,7 @@ /* Regression test: ptr_valid.h documents "Sets errno to EFAULT on * failure", but the /proc/self/maps path and the alignment check * return false without setting errno. Currently fails all 3 tests. */ +#include #include /* Include the C files directly. */ #include diff --git a/ccan/ccan/ptr_valid/test/run-realloc-leak.c b/ccan/ccan/ptr_valid/test/run-realloc-leak.c index f358da16ec36..a91a8fe7c232 100644 --- a/ccan/ccan/ptr_valid/test/run-realloc-leak.c +++ b/ccan/ccan/ptr_valid/test/run-realloc-leak.c @@ -3,6 +3,7 @@ * We interpose malloc/realloc/free by macro (this file includes * ptr_valid.c directly) and inject a failure at each growth call. * Currently fails: not ok 1 and not ok 2. */ +#include #include #include @@ -83,7 +84,6 @@ int main(void) int i; struct ptr_valid_batch batch; - plan_tests(2); alarm(20); /* Silence "defined but not used" when ptr_valid.c's @@ -94,14 +94,23 @@ int main(void) /* Split a 1000-page mapping into alternating RO/RW VMAs so * /proc/self/maps exceeds grab()'s initial 16k buffer and has - * more entries than add_map()'s initial 16 slots. */ + * more entries than add_map()'s initial 16 slots. + * + * Must resolve this (and plan accordingly) before the single + * plan_tests()/plan_skip_all() call tap allows. */ region = mmap(NULL, 1000 * 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); - if (region == MAP_FAILED) + if (region == MAP_FAILED) { plan_skip_all("mmap failed"); + return exit_status(); + } for (i = 0; i < 1000; i += 2) - if (mprotect(region + i * 4096, 4096, PROT_READ) != 0) + if (mprotect(region + i * 4096, 4096, PROT_READ) != 0) { plan_skip_all("mprotect failed"); + return exit_status(); + } + + plan_tests(2); /* grab(): fail the 16384 -> 32768 buffer growth. */ fail_realloc_size = 32768 + 1; diff --git a/ccan/ccan/tal/path/path.c b/ccan/ccan/tal/path/path.c index 941bcd781fa3..16a74266ded0 100644 --- a/ccan/ccan/tal/path/path.c +++ b/ccan/ccan/tal/path/path.c @@ -15,6 +15,7 @@ char *path_cwd(const tal_t *ctx) { size_t len = 64; char *cwd; + int saved_errno = errno; /* *This* is why people hate C. */ cwd = tal_arr(ctx, char, len); @@ -22,6 +23,12 @@ char *path_cwd(const tal_t *ctx) if (errno != ERANGE || !tal_resize(&cwd, len *= 2)) cwd = tal_free(cwd); } + /* No guarantee callers get errno untouched on success, but no + * reason to leave our own retry's stale ERANGE sitting there + * either (cwd longer than our initial 64-byte guess is routine + * under macOS's deep default $TMPDIR). */ + if (cwd) + errno = saved_errno; return cwd; } @@ -113,11 +120,15 @@ struct path_pushd *path_pushd(const tal_t *ctx, const char *dir) if (!old) return NULL; + /* Check this before path_cwd(): we shouldn't be calling getcwd() + * (and thus touching errno) at all when we're not going to do + * anything. */ + if (unlikely(!dir) && taken(dir)) + return tal_free(old); + old->olddir = path_cwd(old); if (unlikely(!old->olddir)) old = tal_free(old); - else if (unlikely(!dir) && is_taken(dir)) - old = tal_free(old); else if (chdir(dir) != 0) old = tal_free(old); diff --git a/ccan/ccan/tal/path/test/run-pushd.c b/ccan/ccan/tal/path/test/run-pushd.c index dc3f2eaefbb7..6afaff2e8f01 100644 --- a/ccan/ccan/tal/path/test/run-pushd.c +++ b/ccan/ccan/tal/path/test/run-pushd.c @@ -6,6 +6,10 @@ int main(void) { struct path_pushd *pd; char path1[1024], path2[1024], *ctx = tal_strdup(NULL, "ctx"); + /* /tmp can be a symlink (e.g. to /private/tmp on macOS): resolve it + * so our getcwd() comparisons match. NULL context: mustn't be a + * child of ctx, since we check ctx has no leftover children below. */ + const char *realtmp = path_canon(NULL, "/tmp"); /* This is how many tests you plan to run */ plan_tests(19); @@ -37,7 +41,7 @@ int main(void) if (!getcwd(path2, sizeof(path2))) abort(); - ok1(streq(path2, "/tmp")); + ok1(streq(path2, realtmp)); path_popd(pd); if (!getcwd(path2, sizeof(path2))) @@ -71,5 +75,6 @@ int main(void) #endif ok1(!tal_first(ctx)); tal_free(ctx); + tal_free(realtmp); return exit_status(); } diff --git a/ccan/ccan/tal/path/test/run-simplify.c b/ccan/ccan/tal/path/test/run-simplify.c index 6b3570103412..d92dcb66a538 100644 --- a/ccan/ccan/tal/path/test/run-simplify.c +++ b/ccan/ccan/tal/path/test/run-simplify.c @@ -4,7 +4,8 @@ int main(void) { - char cwd[1024], *path, *ctx = tal_strdup(NULL, "ctx"); + char cwd[1024], *path, *parent, *tmpbase, *ctx = tal_strdup(NULL, "ctx"); + const char *realtmp; plan_tests(95); @@ -18,6 +19,14 @@ int main(void) if (symlink("run-simplify-foo", "run-simplify-link") != 0) abort(); + /* /tmp can itself be a symlink (e.g. to /private/tmp on macOS), + * which would make path_simplify() keep ".." literally instead of + * collapsing it. Resolve it, so the tests below always exercise a + * real directory. */ + realtmp = path_canon(ctx, "/tmp"); + parent = path_dirname(ctx, realtmp); + tmpbase = path_basename(ctx, realtmp); + /* Handling of . and .. */ path = path_simplify(ctx, "."); ok1(streq(path, ".")); @@ -177,53 +186,53 @@ int main(void) tal_free(path); /* This is expected to be a real directory. */ - path = path_simplify(ctx, "/tmp"); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, realtmp); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/tmp/"); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, take(tal_fmt(ctx, "%s/", realtmp))); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/tmp/."); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, take(tal_fmt(ctx, "%s/.", realtmp))); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/./tmp/."); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, take(tal_fmt(ctx, "/.%s/.", realtmp))); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/../tmp/."); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, take(tal_fmt(ctx, "/..%s/.", realtmp))); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/tmp/.."); - ok1(streq(path, "/")); + path = path_simplify(ctx, take(tal_fmt(ctx, "%s/..", realtmp))); + ok1(streq(path, parent)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/tmp/../"); - ok1(streq(path, "/")); + path = path_simplify(ctx, take(tal_fmt(ctx, "%s/../", realtmp))); + ok1(streq(path, parent)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/tmp/../tmp"); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, take(tal_fmt(ctx, "%s/../%s", realtmp, tmpbase))); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/tmp/../tmp/"); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, take(tal_fmt(ctx, "%s/../%s/", realtmp, tmpbase))); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); - path = path_simplify(ctx, "/tmp/../tmp/."); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, take(tal_fmt(ctx, "%s/../%s/.", realtmp, tmpbase))); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); @@ -255,10 +264,13 @@ int main(void) tal_free(path); /* take tests */ - path = path_simplify(ctx, take(tal_strdup(ctx, "/tmp/../tmp/."))); - ok1(streq(path, "/tmp")); + path = path_simplify(ctx, take(tal_fmt(ctx, "%s/../%s/.", realtmp, tmpbase))); + ok1(streq(path, realtmp)); ok1(tal_parent(path) == ctx); tal_free(path); + tal_free(parent); + tal_free(tmpbase); + tal_free(realtmp); ok1(tal_first(ctx) == NULL); tal_free(ctx); diff --git a/ccan/ccan/time/test/run-divide-roundup.c b/ccan/ccan/time/test/run-divide-roundup.c index 5deb03ebd963..8d5697ba23a6 100644 --- a/ccan/ccan/time/test/run-divide-roundup.c +++ b/ccan/ccan/time/test/run-divide-roundup.c @@ -10,6 +10,7 @@ * After repair it must pass: result well-formed and equal to either * {0, 999999999} (truncation) or {1, 0} (correct round-up). */ +#include #include #include #include diff --git a/ccan/ccan/time/test/run-multiply-overflow.c b/ccan/ccan/time/test/run-multiply-overflow.c index 5b5d74726845..921efef709ae 100644 --- a/ccan/ccan/time/test/run-multiply-overflow.c +++ b/ccan/ccan/time/test/run-multiply-overflow.c @@ -9,6 +9,7 @@ * repair (document the overflow precondition and/or clamp), it must * run UBSan-clean. */ +#include #include #include #include