diff options
| author | Mattias Andrée <m@maandree.se> | 2026-05-08 22:29:35 +0200 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2026-05-08 22:29:35 +0200 |
| commit | 2d3a573977417d917c16742d8d9d8ead047d0ebc (patch) | |
| tree | caeac52856a9df0478e2bee53e5dda1f84422461 | |
| parent | Add DEFAULT_SUPPORT option and improve DEPENDENCIES (diff) | |
| download | librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.gz librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.bz2 librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.xz | |
Misc
Signed-off-by: Mattias Andrée <m@maandree.se>
57 files changed, 2275 insertions, 65 deletions
@@ -74,27 +74,34 @@ SRC =\ $(OBJ:.o=.c)\ $(HDR) +ALL_CFLAGS = $(CFLAGS) $(CFLAGS_MODULES) +ALL_CPPFLAGS = $(CPPFLAGS) $(CPPFLAGS_MODULES) +ALL_LDFLAGS = $(LDFLAGS) $(LDFLAGS_MODULES) + +TEST_INCLUDE_PREFIX = libtest/ +include libtest/config.mk + all: librecrypt.a librecrypt.$(LIBEXT) $(TEST) $(OBJ): $(HDR) $(LOBJ): $(HDR) -$(TOBJ): $(HDR) -$(TEST): librecrypt.a +$(TOBJ): $(HDR) libtest/libtest.h +$(TEST): $(HDR) librecrypt.a libtest/libtest.a libtest/libtest.h .c.o: - $(CC) -c -o $@ $< $(CFLAGS) $(CFLAGS_MODULES) $(CPPFLAGS) $(CPPFLAGS_MODULES) + $(CC) -c -o $@ $< $(ALL_CFLAGS) $(ALL_CPPFLAGS) .c.lo: - $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CFLAGS_MODULES) $(CPPFLAGS) $(CPPFLAGS_MODULES) + $(CC) -fPIC -c -o $@ $< $(ALL_CFLAGS) $(ALL_CPPFLAGS) .c.to: - $(CC) -DTEST -c -o $@ $< $(CFLAGS) $(CFLAGS_MODULES) $(CPPFLAGS) $(CPPFLAGS_MODULES) + $(CC) -DTEST -c -o $@ $< $(ALL_CFLAGS) $(ALL_CPPFLAGS) .to.t: - $(CC) -o $@ $< librecrypt.a $(LDFLAGS) $(LDFLAGS_MODULES) + $(CC) -o $@ $< librecrypt.a libtest/libtest.a $(G) $(ALL_LDFLAGS) $(TEST_LDFLAGS) .c.t: - $(CC) -DTEST -o $@ $< librecrypt.a $(CFLAGS) $(CFLAGS_MODULES) $(CPPFLAGS) $(CPPFLAGS_MODULES) $(LDFLAGS) $(LDFLAGS_MODULES) + $(CC) -DTEST -o $@ $< librecrypt.a libtest/libtest.a $(G) $(ALL_CFLAGS) $(ALL_CPPFLAGS) $(ALL_LDFLAGS) $(TEST_LDFLAGS) librecrypt.a: $(OBJ) @rm -f -- $@ @@ -104,11 +111,18 @@ librecrypt.a: $(OBJ) librecrypt.$(LIBEXT): $(LOBJ) $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS) +libtest/libtest.a: + +cd libtest && $(MAKE) libtest.a + check: $(TEST) + +cd libtest && $(MAKE) check @set -ex;\ for t in $(TEST); do\ $(CHECK_PREFIX) ./$$t;\ done +# Setting CHECK_PREFIX is intended for developers, setting it +# (specially to use valgrind(1)) may limit what the test code +# is able to test install: librecrypt.a librecrypt.$(LIBEXT) mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" diff --git a/algorithms.h b/algorithms.h index c768a4a..d581d73 100644 --- a/algorithms.h +++ b/algorithms.h @@ -7,10 +7,10 @@ /* ordered by preference */ #define LIST_ALGORITHMS(X)\ - X(argon2id)\ - X(argon2d)\ - X(argon2i)\ - X(argon2ds) + X(argon2id) /* argon2 variant:: recommended hybrid */ \ + X(argon2i) /* argon2 variant:: data-independent: weaker against GPU/ASIC, stronger against side-channel attacks */\ + X(argon2d) /* argon2 variant:: data-dependent: weaker against side-channel attacks, stronger against GPU/ASIC */\ + X(argon2ds) /* argon2 variant:: data-dependent though S-box, not properly cryptoanalysed */ diff --git a/argon2/hash.c b/argon2/hash.c index cf83907..5709df0 100644 --- a/argon2/hash.c +++ b/argon2/hash.c @@ -6,19 +6,126 @@ #include <libar2simplified.h> +#define RANGE(MIN, MAX) (uintmax_t)(MIN), (uintmax_t)(MAX) +#define BASE64 librecrypt_common_rfc4848s4_decoding_lut_, argon2__PAD, argon2__STRICT_PAD +#define REMOVE_CONST(X) (*(void **)(void *)&(X)) + + int librecrypt__argon2__hash(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, size_t prefix, void *reserved) { - /* TODO implement */ - (void) out_buffer; - (void) size; - (void) phrase; - (void) len; - (void) settings; - (void) prefix; + struct libar2_argon2_parameters params; + struct libar2_context ctx; + const char *type, *version, *salt_encoded; + uintmax_t mcost, tcost, lanes, saltlen, hashlen; + void *salt = NULL, *scratch = NULL; + size_t scratch_size; + ssize_t r; + int saved_errno; + + /* Not yet used */ (void) reserved; + + /* Gives us memory allocation and threading support; + * so we don't have to implement any of that ourselves */ + libar2simplified_init_context(&ctx); + /* Configure automatic erasure of input memory */ + ctx.autoerase_message = 0; /* allows `phrase` to be read-only */ + ctx.autoerase_secret = 0; /* alloes to params.key, which we are not using, but maybe in the future */ + ctx.autoerase_associated_data = 0; /* alloes to params.ad, which we are not using, but maybe in the future */ + ctx.autoerase_salt = 1; /* since we are decoding the salt, we do a memory allocation, + * and our testing always checks that allocated memory is earse; + * it doesn't really matter, but it's paranoid, and that's good */ + + /* Parse `settings` */ + r = librecrypt_check_settings_(settings, prefix, + "$argon2%^s$%^sm=%^p,t=%^p,p=%^p$%&b$%^h", + &type, "id", "i", "ds", "d", NULL, /* order partially matters */ + &version, "v=19$", "v=16$", "", NULL, + &mcost, RANGE(LIBAR2_MIN_M_COST, LIBAR2_MAX_M_COST), + &tcost, RANGE(LIBAR2_MIN_T_COST, LIBAR2_MAX_T_COST), + &lanes, RANGE(LIBAR2_MIN_LANES, LIBAR2_MAX_LANES), + &salt_encoded, &saltlen, RANGE(LIBAR2_MIN_SALTLEN, LIBAR2_MAX_SALTLEN), BASE64, + &hashlen, RANGE(LIBAR2_MIN_HASHLEN, LIBAR2_MAX_HASHLEN), BASE64); + if (!r) { + errno = EINVAL; + return -1; + } + + /* Decode salt */ + if (!salt_encoded) /* this would be if asterisk-notation is used, but it is not */ + abort(); + r = librecrypt_decode(NULL, 0u, salt_encoded, saltlen, BASE64); + if (r < 0) + return -1; + if (r > 0) { + /* We allow `r` to be 0, although that means saltlen is 0, + * which it cannot actaully be since LIBAR2_MIN_SALTLEN is 8, + * but who knows the future. Of course, we cannot run this + * part if `r` is 0, because we don't want to run malloc(3) + * with 0 because our test's implementation of malloc(3) + * doesn't allow that because it's implementation-defined, + * and we still would have to have a special case handling + * for implementations where it returns NULL, so instead + * we just let `salt` remain NULL, and `saltlen` remain 0. */ + salt = malloc((size_t)r); + if (!salt) + return -1; + if (librecrypt_decode(salt, (size_t)r, salt_encoded, saltlen, BASE64) != r) + abort(); + saltlen = (uintmax_t)r; + } + + /* Apply `settings` */ + params.type = type[1u] == 'd' ? LIBAR2_ARGON2ID : + type[1u] == 's' ? LIBAR2_ARGON2DS : + type[0u] == 'i' ? LIBAR2_ARGON2I : + LIBAR2_ARGON2ID; + params.version = version[3u] == '9' ? LIBAR2_ARGON2_VERSION_13 : /* 19 = 0x13 = 1.3 */ + LIBAR2_ARGON2_VERSION_10; /* 16 = 0x10 = 1.0 */ + params.t_cost = (uint_least32_t)tcost; + params.m_cost = (uint_least32_t)mcost; + params.lanes = (uint_least32_t)lanes; + params.salt = salt; + params.saltlen = (size_t)saltlen; + params.key = NULL; + params.keylen = 0u; + params.ad = NULL; + params.adlen = 0u; + params.hashlen = hashlen ? (size_t)hashlen : argon2__HASH_SIZE; + + /* Argon2 may require a larger buffer to work with for the hash than it outputs */ + scratch_size = libar2_hash_buf_size(¶ms); + if (scratch_size > size) { + scratch = malloc(scratch_size); + if (!scratch) + goto fail; + } + + /* Calculate hash */ + if (libar2_hash(scratch ? scratch : out_buffer, REMOVE_CONST(phrase), len, ¶ms, &ctx)) + goto fail; + + /* same rationale as for `ctx.autoerase_salt = 1;` */ + if (scratch) { + librecrypt_wipe(scratch, scratch_size); + free(scratch); + } else if (scratch_size > params.hashlen) { + librecrypt_wipe(&out_buffer[params.hashlen], scratch_size - params.hashlen); + } + + free(salt); return 0; + +fail: + saved_errno = errno; + if (salt) { + librecrypt_wipe(salt, saltlen); + free(salt); + } + errno = saved_errno; + return -1; } @@ -29,7 +136,9 @@ CONST int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/argon2/is_algorithm.c b/argon2/is_algorithm.c index a725f44..b13c415 100644 --- a/argon2/is_algorithm.c +++ b/argon2/is_algorithm.c @@ -27,6 +27,7 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); #if defined(SUPPORT_ARGON2I) CHECK(argon2i, "", "", 0u); @@ -76,6 +77,7 @@ main(void) CHECK(argon2ds, "$argon2id$", "", 0u); #endif + STOP_RESOURCE_TEST(); return 0; } diff --git a/argon2/make_settings.c b/argon2/make_settings.c index 94ee5ae..66ac3d6 100644 --- a/argon2/make_settings.c +++ b/argon2/make_settings.c @@ -2,11 +2,17 @@ #include "../common.h" #ifndef TEST +#include <libar2.h> + static ssize_t make_settings(char *out_buffer, size_t size, const char *algorithm, size_t memcost, uintmax_t timecost, int gensalt, ssize_t (*rng)(void *out, size_t n, void *user), void *user) { + const char *p, *version = "19"; + size_t algolen, ret, min, len, i; + int r; + /* Use default RNG if NULL is specified */ if (!rng) rng = &librecrypt_rng_; @@ -17,22 +23,90 @@ make_settings(char *out_buffer, size_t size, const char *algorithm, size_t memco memcost = (size_t)4096u; /* 4 MiB */ } else { /* Function takes bytes as memcost, algorithm takes kilobytes */ - int memcost_round_up = memcost % 1024u >= 512u; + size_t memcost_round_up = (memcost >> 9) & 1u; memcost >>= 10; - memcost += memcost_round_up ? 1u : 0u; - memcost = memcost ? memcost : 1u; + memcost += memcost_round_up; } + memcost = memcost < LIBAR2_MIN_M_COST ? LIBAR2_MIN_M_COST : memcost; + memcost = memcost > LIBAR2_MAX_M_COST ? LIBAR2_MAX_M_COST : memcost; - /* TODO implement */ - (void) out_buffer; - (void) size; - (void) algorithm; - (void) memcost; - (void) timecost; - (void) gensalt; - (void) rng; - (void) user; - return 0; + /* Adjust `timecost` for algorithm */ + if (!timecost) { + /* Use default timecost if 0 was specified: + * default m_cost (4096) multiple by default t_cost (10) */ + timecost = 40960u; + } + timecost /= memcost; + timecost = timecost < LIBAR2_MIN_T_COST ? LIBAR2_MIN_T_COST : timecost; + timecost = timecost > LIBAR2_MAX_T_COST ? LIBAR2_MAX_T_COST : timecost; + + /* Get version */ + p = algorithm; + if (*p++ != '$') + abort(); + p = strchr(p, '$'); + algolen = p ? (size_t)(p - algorithm) : strlen(algorithm); + if (algolen > 64) /* just some small value absolute will fit all variants */ + abort(); + if (p++ && *p++ == 'v') { + if (!strncmp(p, "=16", 3u) && (!p[3u] || p[3u] == '$')) + version = "16"; + else if (!strncmp(p, "=19", 3u) && (!p[3u] || p[3u] == '$')) + version = "19"; + else + goto enosys; + } + + /* Write algorithm and parameters */ + r = snprintf(out_buffer, size, "%.*s$v=%s$m=%zu,t=%llu,p=1$", + (int)algolen, algorithm, version, + memcost, (unsigned long long int)timecost); + if (r < (int)sizeof("$argon2_$v=__$m=_,t=_,p=1$") - 1) + abort(); + ret = (size_t)r; + min = size ? ret < size - 1u ? ret : size - 1u : 0u; + out_buffer = &out_buffer[min]; + size -= min; + + /* Add 16 bytes of salt */ + if (gensalt) { + /* 16 bytes is 128 bits, and 128 = 21*6+2, so that is + * 21 full base-64 characeters and 1 that only use 2 bits */ + ret += len = 22u; + min = size ? len < size - 1u ? len : size - 1u : 0u; + if (librecrypt_fill_with_random_(out_buffer, min, rng, user)) + return -1; + if (min == len) + out_buffer[len - 1u] = (char)((unsigned char)out_buffer[len - 1u] & ~15u); + for (i = 0u; i < min; i++) + out_buffer[i] = librecrypt_common_rfc4848s4_encoding_lut_[((unsigned char *)out_buffer)[i]]; + } else { + ret += len = sizeof("*16") - 1u; + min = size ? len < size - 1u ? len : size - 1u : 0u; + memcpy(out_buffer, "*16", min); + } + out_buffer = &out_buffer[min]; + size -= min; + + /* Add tag size (size of hash result) */ + ret += len = sizeof("$*32") - 1u; + min = size ? len < size - 1u ? len : size - 1u : 0u; + memcpy(out_buffer, "$*32", min); + out_buffer = &out_buffer[min]; + size -= min; + + /* NUL terminate */ + if (size) { + /* We were careful to make sure size is positive at + * the end if it was when the function was called */ + *out_buffer = '\0'; + } + + return (ssize_t)ret; + +enosys: + errno = ENOSYS; + return -1; } @@ -59,7 +133,9 @@ CONST int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/argon2/test_supported.c b/argon2/test_supported.c index 4c40ee6..d2eba9a 100644 --- a/argon2/test_supported.c +++ b/argon2/test_supported.c @@ -5,19 +5,20 @@ #include <libar2.h> +#define RANGE(MIN, MAX) (uintmax_t)(MIN), (uintmax_t)(MAX) +#define BASE64 librecrypt_common_rfc4848s4_decoding_lut_, argon2__PAD, argon2__STRICT_PAD + + int librecrypt__argon2__test_supported(const char *phrase, size_t len, int text, const char *settings, size_t prefix, size_t *len_out) { - uintmax_t hashsize; + uintmax_t hashlen; int r; /* We don't care about password content, arbitrary binary is supported */ (void) phrase; (void) text; -#define RANGE(MIN, MAX) (uintmax_t)(MIN), (uintmax_t)(MAX) -#define BASE64 librecrypt_common_rfc4848s4_decoding_lut_, argon2__PAD, argon2__STRICT_PAD - /* Validate string format and parameters */ r = librecrypt_check_settings_(settings, prefix, "$%*$%sm=%p,t=%p,p=%p$%b$%^h", @@ -26,14 +27,14 @@ librecrypt__argon2__test_supported(const char *phrase, size_t len, int text, con RANGE(LIBAR2_MIN_T_COST, LIBAR2_MAX_T_COST), RANGE(LIBAR2_MIN_LANES, LIBAR2_MAX_LANES), RANGE(LIBAR2_MIN_SALTLEN, LIBAR2_MAX_SALTLEN), BASE64, - &hashsize, RANGE(LIBAR2_MIN_HASHLEN, LIBAR2_MAX_HASHLEN), BASE64); + &hashlen, RANGE(LIBAR2_MIN_HASHLEN, LIBAR2_MAX_HASHLEN), BASE64); if (!r) return 0; /* Return hash size */ - if (!hashsize) - hashsize = argon2__HASH_SIZE; - *len_out = (size_t)hashsize; + if (!hashlen) + hashlen = argon2__HASH_SIZE; + *len_out = (size_t)hashlen; /* Check password size */ #if SIZE_MAX > UINT32_MAX @@ -52,7 +53,9 @@ CONST int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } @@ -394,11 +394,15 @@ const struct algorithm *librecrypt_find_first_algorithm_(const char *settings, s * that is length specified using asterisk-notation * "%h" - Same as "%b", except empty content as always allowed unless * asterisk-notation is used - * "%^s" - Same as "%^s" except with output argument - * "%^u" - Same as "%^s" except with output argument - * "%^p" - Same as "%^s" except with output argument - * "%^b" - Same as "%^s" except with output argument - * "%^h" - Same as "%^s" except with output argument + * "%^s" - Same as "%s" except with output argument + * "%^u" - Same as "%u" except with output argument + * "%^p" - Same as "%p" except with output argument + * "%^b" - Same as "%b" except with one output argument: length + * "%&b" - Same as "%b" except with two output argument: + * pointer to text, text length, or NULL and binary length + * "%^h" - Same as "%h" except with one output argument: length + * "%&h" - Same as "%h" except with two output argument: + * pointer to text, text length, or NULL and binary length * @param ... Arguments for each use of '%' in `fmt`: * "%%" - None * "%*" - None @@ -420,9 +424,15 @@ const struct algorithm *librecrypt_find_first_algorithm_(const char *settings, s * "%^b" - Same as "%b" but with an additional argument, as the first one: * a `uintmax_t *` used to store the number of encoded bytes or * the encoded integer after the asterisk if asterisk-encoding is used - * "%^h" - Same as "%h" but with an additional argument, as the first one: - * a `uintmax_t *` used to store the number of encoded bytes or - * the encoded integer after the asterisk if asterisk-encoding is used + * "%&b" - Same as "%b" but with two additional arguments, as the first two: + * a `const char **` and a `uintmax_t *`: if asterisk-notation is used + * the `const char *` will be set to `NULL` and the `uintmax_t` will be + * set to the encoded number, othererwise the `const char *` will be + * set to point to the position in `settings` where the base-64 encoded + * text begins and the `uintmax_t` will be set to length of the text + * (as encoded in base-64, _not_ as decoded to binary) + * "%^h" - Same as "%^b" + * "%&h" - Same as "%&b" * @return 1 if `string` matches `fmt`, 0 otherwise */ LIBRECRYPT_READ_MEM__(1, 2) LIBRECRYPT_NONNULL_I__(3) LIBRECRYPT_WUR__ HIDDEN @@ -431,13 +441,13 @@ int librecrypt_check_settings_(const char *settings, size_t len, const char *fmt #ifdef TEST +# include "libtest/libtest.h" # ifdef __linux__ # include <sys/prctl.h> # endif # include <sys/resource.h> # include <sys/types.h> # include <sys/wait.h> -# include <assert.h> # include <signal.h> # include <string.h> # include <unistd.h> @@ -470,6 +480,21 @@ int librecrypt_check_settings_(const char *settings, size_t len, const char *fmt } while (0) # endif +# define INIT_RESOURCE_TEST()\ + do {\ + libtest_start_tracking();\ + libtest_force_zero_on_alloc(1);\ + libtest_expect_zeroed_on_free(1);\ + } while (0) + +# define STOP_RESOURCE_TEST()\ + do {\ + libtest_stop_tracking();\ + libtest_force_zero_on_alloc(0);\ + libtest_expect_zeroed_on_free(0);\ + EXPECT(libtest_check_no_leaks());\ + } while (0) + # define EXPECT__(EXPR, HOW, RETEXTRACT, RETEXPECT)\ do {\ pid_t pid__;\ @@ -494,7 +519,17 @@ int librecrypt_check_settings_(const char *settings, size_t len, const char *fmt do {\ if (!(EXPR)) {\ fprintf(stderr, "Failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\ + libtest_dump_stack("\t");\ exit(1);\ }\ } while (0) + +# define assert(EXPR)\ + do {\ + if (!(EXPR)) {\ + fprintf(stderr, "Assertion failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\ + libtest_dump_stack("\t");\ + exit(2);\ + }\ + } while (0) #endif diff --git a/config-coverage-gcc.mk b/config-coverage-gcc.mk index 6b9d9bd..4892752 100644 --- a/config-coverage-gcc.mk +++ b/config-coverage-gcc.mk @@ -7,5 +7,7 @@ GCOV = gcov CFLAGS = -g -O0 -pedantic -fprofile-arcs -ftest-coverage LDFLAGS += -lgcov -fprofile-arcs +G = + coverage: check $(GCOV) -pr $(SRC) 2>&1 @@ -7,6 +7,8 @@ CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE CFLAGS = LDFLAGS = +G = -g + DEFAULT_SUPPORT = true # Set to "true" to enable all algorithms that are not explicitly disabled. # Set to "false" to disable all algorithms that are not explicitly enabled. diff --git a/librecrypt.h b/librecrypt.h index 726bfe5..5218152 100644 --- a/librecrypt.h +++ b/librecrypt.h @@ -529,8 +529,8 @@ ssize_t librecrypt_realise_salts(char *restrict out_buffer, size_t size, const c * @throws EINVAL `algorithm` represents a chain of algorithms * @throws ENOSYS `algorithm` represents an algorithm that is not * recognised or was disabled at compile-time - * @throws ENOSYS `algorithm` is `NULL` but all algorithms were disabled at - * compile-time + * @throws ENOSYS `algorithm` is `NULL` but all algorithms were + * disabled at compile-time * * If `rng` is `NULL`, any encountered `EINTR` is ignored, * however, if it is encountered `errno` will be set to `EINTR`, diff --git a/librecrypt_add_algorithm.c b/librecrypt_add_algorithm.c index 167713f..10a2174 100644 --- a/librecrypt_add_algorithm.c +++ b/librecrypt_add_algorithm.c @@ -158,7 +158,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_algorithms_.c b/librecrypt_algorithms_.c index 0213dff..77dfe30 100644 --- a/librecrypt_algorithms_.c +++ b/librecrypt_algorithms_.c @@ -38,6 +38,7 @@ main(void) size_t i; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Validate algorithm entry */ for (i = 0u; i < count; i++) { @@ -89,6 +90,7 @@ main(void) /* Check that the list has an end-of-list marker */ EXPECT(IS_END_OF_ALGORITHMS(&librecrypt_algorithms_[count])); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_chain_length.c b/librecrypt_chain_length.c index 2ce6cf2..e43aaf9 100644 --- a/librecrypt_chain_length.c +++ b/librecrypt_chain_length.c @@ -13,6 +13,7 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Check returns number of '>' plus 1 */ EXPECT(librecrypt_chain_length("") == 1u); @@ -23,6 +24,7 @@ main(void) EXPECT(librecrypt_chain_length("a$b>c$d>e$f>") == 4u); EXPECT(librecrypt_chain_length(">a$b>c$d>e$f>") == 5u); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_check_settings_.c b/librecrypt_check_settings_.c index 9140120..8462b73 100644 --- a/librecrypt_check_settings_.c +++ b/librecrypt_check_settings_.c @@ -60,8 +60,8 @@ check_uint(const char *settings, size_t *off, size_t len, char min_first_digit, /** - * Validate a salt or hash that's either encoded in base-64 - * or has its length encoded using asterisk-notation + * Validate and returns a salt or hash that's either encoded + * in base-64 or has its length encoded using asterisk-notation * * This function does not check the value of any excess bit * in the base-64 encoding @@ -81,7 +81,12 @@ check_uint(const char *settings, size_t *off, size_t len, char min_first_digit, * any other character to the value `0xFF` * @param pad The padding character to used at the end; the NUL byte if none * @param strict_pad Zero if the padding at the end is optional, non-zero otherwise - * @param out Output parameter for the number of bytes used by the salt or hash + * @param strout Output parameter for the beginning of the base-64 text, + * set to `NULL` if asterisk-notation is used + * @param lenout Output parameter for the number of bytes in `*strout`, or if + * `*strout` is set to `NULL`, the asterisk-encoded number; + * however if `strout` is `NULL`, the number bytes used by + * the salt or hash (when in raw binary format) is stored * @return 1 if the encoded value was of proper length, * a proper length was encoded using asterisk-notation, or * if `allow_empty` was non-zero, nothing was encoded; @@ -89,7 +94,8 @@ check_uint(const char *settings, size_t *off, size_t len, char min_first_digit, */ static int check_data(const char *settings, size_t *off, size_t len, uintmax_t min, uintmax_t max, int allow_empty, - const unsigned char dlut[restrict static 256], char pad, int strict_pad, uintmax_t *out) + const unsigned char dlut[restrict static 256], char pad, int strict_pad, + const char **strout, uintmax_t *lenout) { size_t i, old_i; uintmax_t q, r, n; @@ -97,7 +103,9 @@ check_data(const char *settings, size_t *off, size_t len, uintmax_t min, uintmax /* Check for asterisk-notation */ if (*off < len && settings[*off] == '*') { ++*off; - return check_uint(settings, off, len, '0', min, max, out); + if (strout) + *strout = NULL; + return check_uint(settings, off, len, '0', min, max, lenout); } settings = &settings[*off]; @@ -112,8 +120,8 @@ check_data(const char *settings, size_t *off, size_t len, uintmax_t min, uintmax q = i / 4u; r = i % 4u; n = q * 3u + r - (r ? 1u : 0u); - if (out) - *out = n; + if (!strout) + *lenout = n; /* 1 base-64 character in excees of a multiple of 4, * this is illegal because 4 characters encode 3 bytes * without any excees bits (both are 24 bits), so @@ -141,6 +149,12 @@ check_data(const char *settings, size_t *off, size_t len, uintmax_t min, uintmax return 0; } + /* Return the base-64 encoding */ + if (strout) { + *strout = settings; + *lenout = (uintmax_t)i; + } + *off += i; return 1; } @@ -186,6 +200,12 @@ librecrypt_check_settings_(const char *settings, size_t len, const char *fmt, .. fmt++; goto outable; + } else if (fmt[1u] == '&' && (fmt[2u] == 'b' || fmt[2u] == 'h')) { + /* Like "%b"/"%h" except output pointer to base-64 text or decodes asterisk-notation */ + strout = va_arg(args, const char **); + uout = va_arg(args, uintmax_t *); + goto plain_bh; + } else outable: if (fmt[1u] == 'p' || fmt[1u] == 'u') { /* Unsigned integers ("%p" for leading zeros forbidden, "%u" for leading zeros allowed) */ uout = output ? va_arg(args, uintmax_t *) : NULL; @@ -195,15 +215,17 @@ librecrypt_check_settings_(const char *settings, size_t len, const char *fmt, .. return 0; goto outable_done; - } else if (fmt[1u] == 'd' || fmt[1u] == 'h') { - /* Base-64 or asterisk-notation ("%d" for normal, "%h" for "" allowed) */ + } else if (fmt[1u] == 'b' || fmt[1u] == 'h') { + /* Base-64 or asterisk-notation ("%b" for normal, "%h" for "" allowed) */ + strout = NULL; uout = output ? va_arg(args, uintmax_t *) : NULL; + plain_bh: umin = va_arg(args, uintmax_t); umax = va_arg(args, uintmax_t); dlut = va_arg(args, const unsigned char *); pad = (char)va_arg(args, int); /* `char` is promoted to `int` when passed through `...` */ strict_pad = va_arg(args, int); - if (!check_data(settings, &i, len, umin, umax, fmt[1u] == 'h', dlut, pad, strict_pad, uout)) + if (!check_data(settings, &i, len, umin, umax, fmt[1u] == 'h', dlut, pad, strict_pad, strout, uout)) return 0; goto outable_done; @@ -244,9 +266,13 @@ librecrypt_check_settings_(const char *settings, size_t len, const char *fmt, .. #else -CONST int +int main(void) { + SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_common_rfc4848s4_decoding_lut_.c b/librecrypt_common_rfc4848s4_decoding_lut_.c index 457be19..ffef2e3 100644 --- a/librecrypt_common_rfc4848s4_decoding_lut_.c +++ b/librecrypt_common_rfc4848s4_decoding_lut_.c @@ -32,6 +32,7 @@ main(void) unsigned i, found[64u], invalids = 0u; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Ensure values [0, 64) are encoded exactly once each, * and that all other characters are marked as invalid */ @@ -51,6 +52,7 @@ main(void) /* Match with librecrypt_common_rfc4848s4_encoding_lut_ is * tested in librecrypt_common_rfc4848s4_decoding_lut_.c */ + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_common_rfc4848s4_encoding_lut_.c b/librecrypt_common_rfc4848s4_encoding_lut_.c index 89b2feb..2684371 100644 --- a/librecrypt_common_rfc4848s4_encoding_lut_.c +++ b/librecrypt_common_rfc4848s4_encoding_lut_.c @@ -17,6 +17,7 @@ main(void) char c; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); for (i = 0u; i < 64u; i++) { c = librecrypt_common_rfc4848s4_encoding_lut_[i]; @@ -46,6 +47,7 @@ main(void) EXPECT(librecrypt_common_rfc4848s4_decoding_lut_[(unsigned char)c] == i); } + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_crypt.c b/librecrypt_crypt.c index 6192650..a223680 100644 --- a/librecrypt_crypt.c +++ b/librecrypt_crypt.c @@ -17,7 +17,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_decode.c b/librecrypt_decode.c index 847bfea..c7f397b 100644 --- a/librecrypt_decode.c +++ b/librecrypt_decode.c @@ -252,6 +252,7 @@ main(void) int i; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Normal test cases */ check_good_padding = 1; @@ -334,6 +335,7 @@ main(void) EXPECT(errno == EINVAL); } + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_decompose_chain.c b/librecrypt_decompose_chain.c index 9908a5e..ae58c6c 100644 --- a/librecrypt_decompose_chain.c +++ b/librecrypt_decompose_chain.c @@ -43,6 +43,7 @@ main(void) size_t i; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Check HASH_1 with different sizes of `chain` */ stpcpy(buf, HASH_1); @@ -149,6 +150,7 @@ main(void) EXPECT(!strcmp(chain[3u], HASH_3_D)); EXPECT(!strcmp(chain[4u], HASH_3_E)); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_decompose_chain1.c b/librecrypt_decompose_chain1.c index 216c649..6eece8d 100644 --- a/librecrypt_decompose_chain1.c +++ b/librecrypt_decompose_chain1.c @@ -28,6 +28,7 @@ main(void) size_t n; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Check each '>' was replaced with NUL, and number * of '>' plus 1 (number of algorithms) was returned */ @@ -35,6 +36,7 @@ main(void) CHECK(">", "\0", 2u); CHECK("a$b>c$d>e$f", "a$b\0c$d\0e$f", 3u); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_encode.c b/librecrypt_encode.c index 51a0a2e..a15321e 100644 --- a/librecrypt_encode.c +++ b/librecrypt_encode.c @@ -184,6 +184,8 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + CHECK("", ""); CHECK("\x00", "AA"); CHECK("\x00\x00", "AAA"); @@ -192,6 +194,8 @@ main(void) CHECK("testtest", "dGVzdHRlc3Q"); CHECK("zy[]y21 !", "enlbXXkyMSAh"); CHECK("{~|~}~~~\x7f\x7f", "e358fn1+fn5/fw"); + + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_equal.c b/librecrypt_equal.c index 22e2c05..bb3edc6 100644 --- a/librecrypt_equal.c +++ b/librecrypt_equal.c @@ -13,6 +13,8 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + EXPECT(librecrypt_equal("", "") == 1); EXPECT(librecrypt_equal("", "a") == 0); EXPECT(librecrypt_equal("a", "") == 0); @@ -32,6 +34,8 @@ main(void) EXPECT(librecrypt_equal("abcdefg", "abcdef") == 0); EXPECT(librecrypt_equal("abcdef", "") == 0); EXPECT(librecrypt_equal("", "abcdef") == 0); + + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_equal_binary.c b/librecrypt_equal_binary.c index 21d9224..69a9ede 100644 --- a/librecrypt_equal_binary.c +++ b/librecrypt_equal_binary.c @@ -49,6 +49,8 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + EXPECT(librecrypt_equal_binary("", "", 0u) == 1); EXPECT(librecrypt_equal_binary("", "", 1u) == 1); EXPECT(librecrypt_equal_binary("a", "", 1u) == 0); @@ -73,6 +75,8 @@ main(void) EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 1u) == 1); EXPECT(librecrypt_equal_binary("abcdef", "abcdex", 0u) == 1); EXPECT(librecrypt_equal_binary(NULL, NULL, 0u) == 1); + + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_fill_with_random_.c b/librecrypt_fill_with_random_.c index 8eb3cfd..0d8dc78 100644 --- a/librecrypt_fill_with_random_.c +++ b/librecrypt_fill_with_random_.c @@ -95,8 +95,11 @@ main(void) int rv = 0; INIT_TEST_ABORT(); - SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + + /* Check zero-request */ + EXPECT(librecrypt_fill_with_random_(NULL, 0, NULL, NULL) == 0); /* Check default RNG */ EXPECT(librecrypt_fill_with_random_(buf1, sizeof(buf1), NULL, NULL) == 0); @@ -141,6 +144,7 @@ main(void) /* Check function abort(3)s if RNG returns 0 */ EXPECT_ABORT(rv = librecrypt_fill_with_random_(buf1, sizeof(buf1), &zero_ret, NULL)); + STOP_RESOURCE_TEST(); return rv; } diff --git a/librecrypt_find_first_algorithm_.c b/librecrypt_find_first_algorithm_.c index 68cc533..34aa03e 100644 --- a/librecrypt_find_first_algorithm_.c +++ b/librecrypt_find_first_algorithm_.c @@ -44,7 +44,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_get_encoding.c b/librecrypt_get_encoding.c index 2eef882..5867758 100644 --- a/librecrypt_get_encoding.c +++ b/librecrypt_get_encoding.c @@ -40,7 +40,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_hash.c b/librecrypt_hash.c index 267aa31..0d24954 100644 --- a/librecrypt_hash.c +++ b/librecrypt_hash.c @@ -17,7 +17,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_hash_.c b/librecrypt_hash_.c index 563e072..2ee03ee 100644 --- a/librecrypt_hash_.c +++ b/librecrypt_hash_.c @@ -103,7 +103,10 @@ next: for (i = 0u; i < n; i++) if (settings[i] == LIBRECRYPT_HASH_COMPOSITION_DELIMITER) prefix = i + 1u; - /* TODO "_" is a prefix that is being used */ + if (n && !prefix && settings[i] == '_') { + /* Special case for bsdicrypt */ + prefix = 1u; + } if (!algo->flexible_hash_size && prefix != n) goto einval; diff --git a/librecrypt_hash_binary.c b/librecrypt_hash_binary.c index c119c78..089e8be 100644 --- a/librecrypt_hash_binary.c +++ b/librecrypt_hash_binary.c @@ -17,7 +17,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_make_settings.c b/librecrypt_make_settings.c index 49996c1..96ec24a 100644 --- a/librecrypt_make_settings.c +++ b/librecrypt_make_settings.c @@ -47,7 +47,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_next_algorithm.c b/librecrypt_next_algorithm.c index 8a44815..937a2f8 100644 --- a/librecrypt_next_algorithm.c +++ b/librecrypt_next_algorithm.c @@ -75,8 +75,12 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + testcase_1(); testcase_2(); + + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_realise_salts.c b/librecrypt_realise_salts.c index dada7f1..a21c4df 100644 --- a/librecrypt_realise_salts.c +++ b/librecrypt_realise_salts.c @@ -164,7 +164,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_rng_.c b/librecrypt_rng_.c index d7fbee3..d53b999 100644 --- a/librecrypt_rng_.c +++ b/librecrypt_rng_.c @@ -171,6 +171,7 @@ main(void) void *user = NULL; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Check that output is random */ n1 = librecrypt_rng_(buf1, sizeof(buf1), NULL); @@ -182,6 +183,7 @@ main(void) /* Check zero-request */ EXPECT(librecrypt_rng_(NULL, 0u, NULL) == 0u); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_settings_prefix.c b/librecrypt_settings_prefix.c index 7568bb3..0fcf8ae 100644 --- a/librecrypt_settings_prefix.c +++ b/librecrypt_settings_prefix.c @@ -21,7 +21,9 @@ librecrypt_settings_prefix(const char *hash, size_t *hashsize_out) /* and get `strlen(hash)` */ len = i; - /* TODO "_" is a prefix that is being used */ + /* Special case for bsdicrypt */ + if (ret == last_offset && ret < len && hash[ret] == '_') + ret += 1u; /* Return if hash size is not required */ if (!hashsize_out) @@ -87,6 +89,7 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Simple cases */ CHECK_NULL("", "result"); @@ -107,8 +110,14 @@ main(void) CHECK_NULL("a$b$c>", "*2"); CHECK_NULL("a$b$c>*10$", "*2"); + /* Special case: bsdicrypt */ + CHECK_NULL("_", "res"); + CHECK_NULL("_", ""); + CHECK_NULL("$x$*10>_", "_"); + /* TODO test hash size output (requires algorithms) */ + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_test_supported.c b/librecrypt_test_supported.c index 71daad1..a0f7d73 100644 --- a/librecrypt_test_supported.c +++ b/librecrypt_test_supported.c @@ -47,7 +47,9 @@ int main(void) { SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_wipe.c b/librecrypt_wipe.c index a704ef0..ac99d63 100644 --- a/librecrypt_wipe.c +++ b/librecrypt_wipe.c @@ -44,6 +44,7 @@ main(void) char *buf; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); #if defined(__GNUC__) # pragma GCC diagnostic ignored "-Wnonnull" @@ -56,8 +57,9 @@ main(void) buf = malloc(256u); memset(buf, 99, 256u); librecrypt_wipe(buf, 256u); - free(buf); /* TODO should test memory is wiped */ + free(buf); + STOP_RESOURCE_TEST(); return 0; } diff --git a/librecrypt_wipe_str.c b/librecrypt_wipe_str.c index 81f9d54..75a4ade 100644 --- a/librecrypt_wipe_str.c +++ b/librecrypt_wipe_str.c @@ -27,6 +27,7 @@ main(void) size_t i; SET_UP_ALARM(); + INIT_RESOURCE_TEST(); /* Check NULL is supported */ librecrypt_wipe_str(NULL); @@ -38,6 +39,7 @@ main(void) CHECK(" hello developer "); CHECK("\1 hello developer \1"); + STOP_RESOURCE_TEST(); return 0; } diff --git a/libtest/Makefile b/libtest/Makefile new file mode 100644 index 0000000..1bb870b --- /dev/null +++ b/libtest/Makefile @@ -0,0 +1,78 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OS = linux +# Linux: linux +# Mac OS: macos +# Windows: windows +include ../mk/$(OS).mk + + +OBJ =\ + alloc.o\ + alloc_have_custom.o\ + globals.o\ + libtest_alloc.o\ + libtest_base_pointer.o\ + libtest_check_no_leaks.o\ + libtest_expect_zeroed_on_free.o\ + libtest_force_zero_on_alloc.o\ + libtest_free.o\ + libtest_get_pagesize.o\ + libtest_start_tracking.o\ + libtest_stop_tracking.o\ + libtest_dump_stack.o\ + $(OBJ_BACKTRACE) + +HDR =\ + libtest.h\ + common.h\ + +LOBJ = $(OBJ:.o=.lo) +TOBJ = $(OBJ:.o=.to) +TEST = $(OBJ:.o=.t) + + +all: libtest.a $(TEST) +$(OBJ): $(HDR) +$(LOBJ): $(HDR) +$(TOBJ): $(HDR) +$(TEST): $(HDR) libtest.a + +.c.o: + $(C17) -fno-builtin -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS) + +.c.lo: + $(C17) -fno-builtin -fPIC -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS) + +.c.to: + $(C17) -DTEST -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS) + +.to.t: + $(C17) -o $@ $< libtest.a $(TEST_LDFLAGS) + +.c.t: + $(C17) -DTEST -o $@ $< libtest.a $(TEST_CFLAGS) $(TEST_CPPFLAGS) $(TEST_LDFLAGS) + +libtest.a: $(OBJ) + @rm -f -- $@ + $(AR) rc $@ $(OBJ) + $(AR) ts $@ > /dev/null + +check: $(TEST) + @set -ex;\ + for t in $(TEST); do\ + ./$$t;\ + done + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib + -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) + -rm -f -- *.to *.t + +.SUFFIXES: +.SUFFIXES: .lo .o .c .to .t + +.PHONY: all check install uninstall clean diff --git a/libtest/alloc.c b/libtest/alloc.c new file mode 100644 index 0000000..842f856 --- /dev/null +++ b/libtest/alloc.c @@ -0,0 +1,505 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +static void * +common_malloc(size_t n, enum memory_origin origin) +{ + struct meminfo meminfo; + + assert(n); + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = n; + meminfo.alignment_type = DEFAULT_ALIGNMENT; + meminfo.initialised_on_alloc = 0; + meminfo.origin = origin; + + return libtest_alloc(&meminfo); +} + + +static void * +common_realloc(void *old_ptr, size_t new_n, enum memory_origin origin) +{ + void *new_ptr; + size_t old_n; + + assert(new_n); + if (!old_ptr) + return common_malloc(new_n, origin); + + assert(GET_MEMINFO(old_ptr)->requested_alignment <= _Alignof(max_align_t)); + + new_ptr = common_malloc(new_n, origin); /* always change the pointer */ + if (!new_ptr) + return NULL; + + new_n = GET_MEMINFO(new_ptr)->usable_alloc_size; + old_n = GET_MEMINFO(old_ptr)->usable_alloc_size; + memcpy(new_ptr, old_ptr, new_n < old_n ? new_n : old_n); + libtest_free(old_ptr, DO_NOT_REQUIRE_ZEROED); + + return new_ptr; +} + + +static void * +common_memalign(size_t alignment, size_t size, enum memory_origin origin) +{ + struct meminfo meminfo; + + assert(size); + assert(alignment); + assert(!(alignment & (alignment - 1u))); + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = size; + meminfo.alignment_type = CUSTOM_ALIGNMENT; + meminfo.requested_alignment = alignment; + meminfo.initialised_on_alloc = 0; + meminfo.origin = origin; + + return libtest_alloc(&meminfo); +} + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wmissing-prototypes" +#endif + + +void * +(malloc)(size_t n) +{ + libtest_malloc_is_custom = 1; + assert(libtest_have_custom_free()); + + return common_malloc(n, FROM_MALLOC); +} + + +void * +(calloc)(size_t n, size_t m) +{ + struct meminfo meminfo; + + libtest_calloc_is_custom = 1; + assert(libtest_have_custom_free()); + + assert(n); + assert(m); + + if (n > SIZE_MAX / m) { + errno = ENOMEM; + return NULL; + } + n *= m; + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = n; + meminfo.alignment_type = DEFAULT_ALIGNMENT; + meminfo.initialised_on_alloc = 1; + meminfo.origin = FROM_CALLOC; + + return libtest_alloc(&meminfo); +} + + +void * +(realloc)(void *old_ptr, size_t new_n) +{ + libtest_realloc_is_custom = 1; + assert(libtest_have_custom_free()); + + return common_realloc(old_ptr, new_n, FROM_REALLOC); +} + + +void * +(reallocarray)(void *old_ptr, size_t new_n, size_t new_m) +{ + libtest_reallocarray_is_custom = 1; + assert(libtest_have_custom_free()); + + assert(new_n); + assert(new_m); + + if (new_n > SIZE_MAX / new_m) { + errno = ENOMEM; + return NULL; + } + + return common_realloc(old_ptr, new_n * new_m, FROM_REALLOCARRAY); +} + + +void * +(valloc)(size_t size) +{ + struct meminfo meminfo; + + libtest_valloc_is_custom = 1; + assert(libtest_have_custom_free()); + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = size; + meminfo.alignment_type = PAGE_ALIGNMENT; + meminfo.initialised_on_alloc = 0; + meminfo.origin = FROM_VALLOC; + + return libtest_alloc(&meminfo); +} + + +void * +(pvalloc)(size_t size) +{ + struct meminfo meminfo; + void *ptr; + size_t pagesize; + + libtest_pvalloc_is_custom = 1; + assert(libtest_have_custom_free()); + + pagesize = libtest_get_pagesize(); + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = size + (pagesize - size % pagesize) % pagesize; + meminfo.alignment_type = PAGE_ALIGNMENT; + meminfo.initialised_on_alloc = 0; + meminfo.origin = FROM_PVALLOC; + + ptr = libtest_alloc(&meminfo); + if (ptr) + GET_MEMINFO(ptr)->requested_alloc_size = size; + return ptr; +} + + +void * +(memalign)(size_t alignment, size_t size) +{ + libtest_memalign_is_custom = 1; + assert(libtest_have_custom_free()); + + return common_memalign(alignment, size, FROM_MEMALIGN); +} + + +void * +(aligned_alloc)(size_t alignment, size_t size) +{ + libtest_aligned_alloc_is_custom = 1; + assert(libtest_have_custom_free()); + + assert(alignment); + assert(!(size % alignment)); + + return common_memalign(alignment, size, FROM_ALIGNED_ALLOC); +} + + +int +(posix_memalign)(void **memptr, size_t alignment, size_t size) +{ + struct meminfo meminfo; + int err, saved_errno = errno; + void *ptr; + + libtest_posix_memalign_is_custom = 1; + assert(libtest_have_custom_free()); + + assert(size); + assert(alignment); + assert(!(alignment & (alignment - 1u))); + assert(!(alignment & (sizeof(void *) - 1u))); + + memset(&meminfo, 0, sizeof(meminfo)); + meminfo.requested_alloc_size = size; + meminfo.alignment_type = CUSTOM_ALIGNMENT; + meminfo.requested_alignment = alignment; + meminfo.initialised_on_alloc = 0; + meminfo.origin = FROM_POSIX_MEMALIGN; + + ptr = libtest_alloc(&meminfo); + err = ptr ? 0 : errno; + if (!err) + *memptr = ptr; + + errno = saved_errno; + return err; +} + + +PURE +size_t +(malloc_usable_size)(void *ptr) +{ + libtest_malloc_usable_size_is_custom = 1; + assert(libtest_have_custom_malloc()); + + return ptr ? GET_MEMINFO(ptr)->usable_alloc_size : 0u; +} + + +void +(free)(void *ptr) +{ + libtest_free_is_custom = 1; + + if (ptr) { + struct meminfo *meminfo; + meminfo = GET_MEMINFO(ptr); + if (!meminfo->accept_leakage) { + assert(meminfo->origin != FROM_VALLOC); + assert(meminfo->origin != FROM_PVALLOC); + } + } + + libtest_free(ptr, REQUIRE_ZEROED); +} + + +void +(free_sized)(void *ptr, size_t size) +{ + libtest_free_sized_is_custom = 1; + + if (ptr) { + struct meminfo *meminfo; + meminfo = GET_MEMINFO(ptr); + assert(meminfo->alignment_type == DEFAULT_ALIGNMENT); + assert(meminfo->requested_alloc_size == size); + } + + libtest_free(ptr, REQUIRE_ZEROED); +} + + +void +(free_aligned_sized)(void *ptr, size_t alignment, size_t size) +{ + libtest_free_aligned_sized_is_custom = 1; + + if (ptr) { + struct meminfo *meminfo; + meminfo = GET_MEMINFO(ptr); + assert(meminfo->alignment_type == CUSTOM_ALIGNMENT); + assert(meminfo->requested_alloc_size == size); + assert(meminfo->requested_alignment == alignment); + } + + libtest_free(ptr, REQUIRE_ZEROED); +} + + +#else + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wredundant-decls" +#endif + + +void *(malloc)(size_t n); +void *(calloc)(size_t n, size_t m); +void *(realloc)(void *old_ptr, size_t new_n); +void *(reallocarray)(void *old_ptr, size_t new_n, size_t new_m); +void *(valloc)(size_t size); +void *(pvalloc)(size_t size); +void *(memalign)(size_t alignment, size_t size); +void *(aligned_alloc)(size_t alignment, size_t size); +int (posix_memalign)(void **memptr, size_t alignment, size_t size); +size_t (malloc_usable_size)(void *ptr); +void (free)(void *ptr); +void (free_sized)(void *ptr, size_t size); +void (free_aligned_sized)(void *ptr, size_t alignment, size_t size); + + +static void +fill_memory(uint8_t *mem, size_t n) +{ + size_t i; + for (i = 0u; i < n; i++) + mem[i] = (uint8_t)i; +} + + +static int +check_memory(uint8_t *mem, size_t filled, size_t zeroed) +{ + size_t i; + uint8_t bad = 0; + for (i = 0u; i < filled; i++) + bad |= (uint8_t)(mem[i] ^ (uint8_t)i); + for (i = filled; i < filled + zeroed; i++) + bad |= mem[i]; + return !bad; +} + + +#define p libtest_p___ +extern void *volatile p; +void *volatile p; + + +static void +check(int use_free) +{ + void *q; + + p = malloc(10u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 10u); + assert(malloc_usable_size(p) >= 10u); + if (use_free) + free(p); + else + free_sized(p, 10u); + + p = calloc(3u, 4u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 12u); + assert(malloc_usable_size(p) >= 12u); + if (use_free) + free(p); + else + free_sized(p, 12u); + + p = realloc(NULL, 5u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 5u); + assert(malloc_usable_size(p) >= 5u); + fill_memory(p, 5u); + + p = realloc(p, 9u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 9u); + assert(malloc_usable_size(p) >= 9u); + assert(check_memory(p, 5u, 4u)); + + p = realloc(p, 3u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 3u); + assert(malloc_usable_size(p) >= 3u); + assert(check_memory(p, 3u, 0u)); + if (use_free) + free(p); + else + free_sized(p, 3u); + + p = reallocarray(NULL, 3u, 10u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 30u); + assert(malloc_usable_size(p) >= 30u); + fill_memory(p, 30u); + + p = reallocarray(p, 10u, 10u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 100u); + assert(malloc_usable_size(p) >= 100u); + assert(check_memory(p, 30u, 70u)); + + p = reallocarray(p, 5u, 10u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t)); + assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 50u); + assert(malloc_usable_size(p) >= 50u); + assert(check_memory(p, 30u, 20u)); + if (use_free) + free(p); + else + free_sized(p, 50u); + + p = memalign(2u, 7u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == 2u); + assert((uintptr_t)p % 2u == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 7u); + assert(malloc_usable_size(p) >= 7u); + if (use_free) + free(p); + else + free_aligned_sized(p, 2u, 7u); + + p = aligned_alloc(2u, 4u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == 2u); + assert((uintptr_t)p % 2u == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 4u); + assert(malloc_usable_size(p) >= 4u); + if (use_free) + free(p); + else + free_aligned_sized(p, 2u, 4u); + + assert(!posix_memalign(&q, sizeof(void *), 11u)); + p = q; + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == sizeof(void *)); + assert((uintptr_t)p % 2u == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 11u); + assert(malloc_usable_size(p) >= 11u); + if (use_free) + free(p); + else + free_aligned_sized(p, sizeof(void *), 11u); +} + + +int +main(void) +{ + size_t pagesize; + + SET_UP_ALARM(); + + libtest_start_tracking(); + + check(1); + check(0); + + pagesize = libtest_get_pagesize(); + + libtest_stop_tracking(); + + p = valloc(6u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == pagesize); + assert((uintptr_t)p % (uintptr_t)pagesize == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 6u); + assert(malloc_usable_size(p) >= 6u); + /* cannot be free(3)ed */ + + p = pvalloc(8u); + assert(p); + assert(GET_MEMINFO(p)->requested_alignment == pagesize); + assert((uintptr_t)p % (uintptr_t)pagesize == 0u); + assert(GET_MEMINFO(p)->requested_alloc_size == 8u); + assert(malloc_usable_size(p) >= pagesize); + /* cannot be free(3)ed */ + + assert(libtest_check_no_leaks()); + return 0; + + /* TODO test failures */ +} + + +#endif diff --git a/libtest/alloc_have_custom.c b/libtest/alloc_have_custom.c new file mode 100644 index 0000000..ae67be0 --- /dev/null +++ b/libtest/alloc_have_custom.c @@ -0,0 +1,236 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wredundant-decls" +#endif + +void *(malloc)(size_t n); +void *(calloc)(size_t n, size_t m); +void *(realloc)(void *old_ptr, size_t new_n); +void *(reallocarray)(void *old_ptr, size_t new_n, size_t new_m); +void *(valloc)(size_t size); +void *(pvalloc)(size_t size); +void *(memalign)(size_t alignment, size_t size); +void *(aligned_alloc)(size_t alignment, size_t size); +int (posix_memalign)(void **memptr, size_t alignment, size_t size); +PURE size_t (malloc_usable_size)(void *ptr); +void (free)(void *ptr); +void (free_sized)(void *ptr, size_t size); +void (free_aligned_sized)(void *ptr, size_t alignment, size_t size); + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + +#define CHECK_CUSTOM_ALLOC(WHAT, ...)\ + do {\ + int r = libtest_##WHAT##_is_custom;\ + if (r < 0) {\ + void *p = (WHAT)(__VA_ARGS__);\ + free(p);\ + r = libtest_##WHAT##_is_custom;\ + if (r < 0)\ + libtest_##WHAT##_is_custom = r = 0;\ + }\ + assert(r == 0 || r == 1);\ + return r;\ + } while (0) + + +static void * +malloc_with_realloc(size_t n) +{ + void *volatile ptr = malloc(1u); + assert(ptr); + ptr = realloc(ptr, n); + assert(ptr); + return ptr; +} + + +static void * +malloc_with_reallocarray(size_t n, size_t m) +{ + void *volatile ptr = malloc(1u); + assert(ptr); + ptr = reallocarray(ptr, n, m); + assert(ptr); + return ptr; +} + + +static void * +freeable_valloc(size_t n) +{ + void *ptr = valloc(n); + if (ptr) { + GET_MEMINFO(ptr)->origin = FROM_ALIGNED_ALLOC; + GET_MEMINFO(ptr)->alignment_type = CUSTOM_ALIGNMENT; + } + return ptr; +} + + +static void * +freeable_pvalloc(size_t n) +{ + void *ptr = pvalloc(n); + if (ptr) { + GET_MEMINFO(ptr)->origin = FROM_ALIGNED_ALLOC; + GET_MEMINFO(ptr)->alignment_type = CUSTOM_ALIGNMENT; + } + return ptr; +} + + +#define libtest_malloc_with_realloc_is_custom libtest_realloc_is_custom +#define libtest_malloc_with_reallocarray_is_custom libtest_reallocarray_is_custom +#define libtest_freeable_valloc_is_custom libtest_valloc_is_custom +#define libtest_freeable_pvalloc_is_custom libtest_pvalloc_is_custom +int libtest_have_custom_malloc(void) { CHECK_CUSTOM_ALLOC(malloc, 1u); } +int libtest_have_custom_calloc(void) { CHECK_CUSTOM_ALLOC(calloc, 1u, 1u); } +int libtest_have_custom_realloc(void) { CHECK_CUSTOM_ALLOC(malloc_with_realloc, 1u); } +int libtest_have_custom_reallocarray(void) { CHECK_CUSTOM_ALLOC(malloc_with_reallocarray, 1u, 1u); } +int libtest_have_custom_valloc(void) { CHECK_CUSTOM_ALLOC(freeable_valloc, 1u); } +int libtest_have_custom_pvalloc(void) { CHECK_CUSTOM_ALLOC(freeable_pvalloc, 1u); } +int libtest_have_custom_memalign(void) { CHECK_CUSTOM_ALLOC(memalign, 1u, 1u); } +int libtest_have_custom_aligned_alloc(void) { CHECK_CUSTOM_ALLOC(aligned_alloc, 1u, 1u); } + + +int +libtest_have_custom_posix_memalign(void) +{ + int r = libtest_posix_memalign_is_custom; + if (r < 0) { + void *p = NULL; + r = (posix_memalign)(&p, sizeof(p), 1u); + if (r) + free(p); + r = libtest_posix_memalign_is_custom; + if (r < 0) + libtest_posix_memalign_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +static volatile size_t libtest_have_custom_malloc_usable_size_discard; + +int +libtest_have_custom_malloc_usable_size(void) +{ + int r = libtest_malloc_usable_size_is_custom; + if (r < 0) { + void *p = (malloc)(1u); + assert(p); + libtest_have_custom_malloc_usable_size_discard = malloc_usable_size(p); + (free)(p); + r = libtest_malloc_usable_size_is_custom; + if (r < 0) + libtest_malloc_usable_size_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +int +libtest_have_custom_free(void) +{ + int r = libtest_free_is_custom; + void *p; + if (r < 0) { + libtest_free_is_custom = 1; + p = (malloc)(1u); + assert(p); + assert(libtest_malloc_is_custom == 1); + (free)(p); + r = libtest_free_is_custom; + if (r < 0) + libtest_free_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +int +libtest_have_custom_free_sized(void) +{ + int r = libtest_free_sized_is_custom; + void *p; + if (r < 0) { + libtest_free_sized_is_custom = 1; + p = (malloc)(1u); + assert(p); + assert(libtest_malloc_is_custom == 1); + (free_sized)(p, 1u); + r = libtest_free_sized_is_custom; + if (r < 0) + libtest_free_sized_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +int +libtest_have_custom_free_aligned_sized(void) +{ + int r = libtest_free_aligned_sized_is_custom; + void *p; + if (r < 0) { + libtest_free_aligned_sized_is_custom = 1; + p = (aligned_alloc)(1u, 1u); + assert(p); + assert(libtest_aligned_alloc_is_custom == 1); + (free_aligned_sized)(p, 1u, 1u); + r = libtest_free_aligned_sized_is_custom; + if (r < 0) + libtest_free_aligned_sized_is_custom = r = 0; + } + assert(r == 0 || r == 1); + return r; +} + + +#else + + +#define CHECK(WHAT)\ + do {\ + fprintf(stderr, "testing %s\n", #WHAT);\ + EXPECT(WHAT() == 1);\ + } while (0) + + +int +main(void) +{ + SET_UP_ALARM(); + + CHECK(libtest_have_custom_malloc); + CHECK(libtest_have_custom_calloc); + CHECK(libtest_have_custom_realloc); + CHECK(libtest_have_custom_reallocarray); + CHECK(libtest_have_custom_valloc); + CHECK(libtest_have_custom_pvalloc); + CHECK(libtest_have_custom_memalign); + CHECK(libtest_have_custom_aligned_alloc); + CHECK(libtest_have_custom_posix_memalign); + CHECK(libtest_have_custom_malloc_usable_size); + CHECK(libtest_have_custom_free); + CHECK(libtest_have_custom_free_sized); + CHECK(libtest_have_custom_free_aligned_sized); + + return 0; +} + + +#endif diff --git a/libtest/common.h b/libtest/common.h new file mode 100644 index 0000000..070b217 --- /dev/null +++ b/libtest/common.h @@ -0,0 +1,242 @@ +/* See LICENSE file for copyright and license details. */ +#ifdef WITH_BACKTRACE +# define UNW_LOCAL_ONLY +# include <libunwind.h> +# include <elfutils/libdwfl.h> +#endif +#include <sys/mman.h> +#include <errno.h> +#include <inttypes.h> +#include <stdatomic.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libtest.h" + + +#if __STDC_VERSION__ >= 202311L +# define _Alignof alignof +# define _Alignas alignas +# define _Thread_local thread_local +#endif + + +#if defined(__GNUC__) +# define HIDDEN __attribute__((__visibility__("hidden"))) +# define CONST __attribute__((__const__)) +# define PURE __attribute__((__pure__)) +#else +# define HIDDEN +# define CONST +# define PURE +#endif + + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wpadded" +#endif + + +#ifndef MAP_UNINITIALIZED +# define MAP_UNINITIALIZED 0 +#endif + + +#define ELEMSOF(A) (sizeof(A) / sizeof(*(A))) + + +#define BASE_POINTER(PTR) (*libtest_base_pointer((PTR))) +#define GET_MEMINFO(PTR) ((struct meminfo *)BASE_POINTER(PTR)) +#define SPINLOCK(LOCK) do {} while (atomic_flag_test_and_set_explicit(&(LOCK), memory_order_acquire)) +#define SPINUNLOCK(LOCK) atomic_flag_clear_explicit(&(LOCK), memory_order_release) + + +enum libtest_zero_check { + DO_NOT_REQUIRE_ZEROED, + REQUIRE_ZEROED +}; + +enum align_type { + DEFAULT_ALIGNMENT, + PAGE_ALIGNMENT, + CUSTOM_ALIGNMENT +}; + +enum memory_origin { + FROM_MALLOC, + FROM_CALLOC, + FROM_REALLOC, + FROM_REALLOCARRAY, + FROM_VALLOC, + FROM_PVALLOC, + FROM_MEMALIGN, + FROM_ALIGNED_ALLOC, + FROM_POSIX_MEMALIGN, + FROM_MMAP_FILE, + FROM_MMAP_ANON +}; + +#ifdef WITH_BACKTRACE +struct backtrace { + size_t n; + Dwarf_Addr trace[]; +}; +#endif + +struct meminfo { +#ifdef WITH_BACKTRACE + struct backtrace *backtrace; +#endif + struct meminfo *next, *prev; + void *usable_area; + size_t real_alloc_size; + size_t usable_alloc_size; + size_t requested_alloc_size; + size_t requested_alignment; + size_t actual_alignment; + enum align_type alignment_type; + int initialised_on_alloc; + enum memory_origin origin; + int accept_leakage; /* implies DO_NOT_REQUIRE_ZEROED */ +}; + + +extern volatile int libtest_malloc_is_custom; +extern volatile int libtest_calloc_is_custom; +extern volatile int libtest_realloc_is_custom; +extern volatile int libtest_reallocarray_is_custom; +extern volatile int libtest_valloc_is_custom; +extern volatile int libtest_pvalloc_is_custom; +extern volatile int libtest_memalign_is_custom; +extern volatile int libtest_aligned_alloc_is_custom; +extern volatile int libtest_posix_memalign_is_custom; +extern volatile int libtest_malloc_usable_size_is_custom; +extern volatile int libtest_free_is_custom; +extern volatile int libtest_free_sized_is_custom; +extern volatile int libtest_free_aligned_sized_is_custom; + +extern struct meminfo libtest_allocs_head; +extern struct meminfo libtest_allocs_tail; +extern int libtest_allocs_list_inited; +extern atomic_flag libtest_allocs_list_spinlock; + +extern int libtest_zero_on_alloc; +extern int libtest_expect_zeroed; +extern int libtest_malloc_accept_leakage; + +extern _Thread_local size_t libtest_malloc_internal_usage; +extern _Thread_local size_t libtest_kill_malloc_tracking; + + +HIDDEN inline void ** +libtest_base_pointer(void *ptr) +{ + uintptr_t addr = (uintptr_t)ptr - (uintptr_t)sizeof(void *); + addr -= addr % _Alignof(void *); + return (void **)addr; +} + + +HIDDEN size_t libtest_get_pagesize(void); +HIDDEN void *libtest_alloc(struct meminfo *); +HIDDEN void libtest_free(void *, enum libtest_zero_check); + +#ifdef WITH_BACKTRACE +HIDDEN void libtest_print_backtrace(FILE *, const char *indent, size_t first, const struct backtrace *); +#else +# define libtest_print_backtrace(FP, INDENT, FIRST, BACKTRACE) ((void)0) +#endif + + +void *__mmap(void *, size_t, int, int, int, off_t); +int __munmap(void *, size_t); +void *__mremap(void *, size_t, size_t, int, ...); +#define libtest_real_mmap __mmap +#define libtest_real_munmap __munmap +#define libtest_real_mremap __mremap + + +#define assert(EXPR)\ + do {\ + if (!(EXPR)) {\ + libtest_malloc_internal_usage++;\ + fprintf(stderr, "Assetion failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\ + libtest_print_backtrace(stderr, "\t", 0u, NULL);\ + exit(2);\ + }\ + } while (0) + + +#ifdef TEST +# ifdef __linux__ +# include <sys/prctl.h> +# endif +# include <sys/resource.h> +# include <sys/types.h> +# include <sys/wait.h> +# include <signal.h> +# include <string.h> +# include <unistd.h> + +# define SET_UP_ALARM()\ + do {\ + unsigned int alarm_time__ = alarm(10u);\ + if (alarm_time__ > 10u)\ + alarm(alarm_time__);\ + } while (0) + +# if defined(PR_SET_DUMPABLE) +# define INIT_TEST_ABORT()\ + do {\ + struct rlimit rl__;\ + rl__.rlim_cur = 0;\ + rl__.rlim_max = 0;\ + (void) setrlimit(RLIMIT_CORE, &rl__);\ + (void) prctl(PR_SET_DUMPABLE, 0);\ + EXPECT_ABORT(abort());\ + } while (0) +# else +# define INIT_TEST_ABORT()\ + do {\ + struct rlimit rl__;\ + rl__.rlim_cur = 0;\ + rl__.rlim_max = 0;\ + (void) setrlimit(RLIMIT_CORE, &rl__);\ + EXPECT_ABORT(abort());\ + } while (0) +# endif + +# define EXPECT__(EXPR, HOW, RETEXTRACT, RETEXPECT)\ + do {\ + pid_t pid__;\ + int status__;\ + pid__ = fork();\ + EXPECT(pid__ != -1);\ + if (pid__ == 0) {\ + (EXPR);\ + _exit(0);\ + }\ + EXPECT(waitpid(pid__, &status__, 0) == pid__);\ + EXPECT(HOW(status__));\ + EXPECT(RETEXTRACT(status__) == RETEXPECT);\ + } while (0) + +# define EXPECT_ABORT(EXPR)\ + do {\ + EXPECT__(EXPR, WIFSIGNALED, WTERMSIG, SIGABRT);\ + } while (0) + +# define EXPECT(EXPR)\ + do {\ + if (!(EXPR)) {\ + libtest_malloc_internal_usage++;\ + fprintf(stderr, "Failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\ + libtest_print_backtrace(stderr, "\t", 0u, NULL);\ + exit(1);\ + }\ + } while (0) +#endif diff --git a/libtest/config.mk b/libtest/config.mk new file mode 100644 index 0000000..b2519f0 --- /dev/null +++ b/libtest/config.mk @@ -0,0 +1,4 @@ +WITH_BACKTRACE = true + +TEST_CONFIGFILE = config_backtraces=$(WITH_BACKTRACE).mk +include $(TEST_INCLUDE_PREFIX)$(TEST_CONFIGFILE) diff --git a/libtest/config_backtraces=false.mk b/libtest/config_backtraces=false.mk new file mode 100644 index 0000000..f508390 --- /dev/null +++ b/libtest/config_backtraces=false.mk @@ -0,0 +1,10 @@ +C17 !=\ + if command -v c17 >/dev/null || ! command -v cc >/dev/null; then\ + echo c17;\ + else\ + echo cc -std=c17;\ + fi + +TEST_CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +TEST_CFLAGS = +TEST_LDFLAGS = diff --git a/libtest/config_backtraces=true.mk b/libtest/config_backtraces=true.mk new file mode 100644 index 0000000..8233aaa --- /dev/null +++ b/libtest/config_backtraces=true.mk @@ -0,0 +1,12 @@ +C17 !=\ + if command -v c17 >/dev/null || ! command -v cc >/dev/null; then\ + echo c17;\ + else\ + echo cc -std=c17;\ + fi + +TEST_CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -DWITH_BACKTRACE +TEST_CFLAGS = -g +TEST_LDFLAGS = -lunwind -ldw + +OBJ_BACKTRACE = libtest_print_backtrace.o diff --git a/libtest/globals.c b/libtest/globals.c new file mode 100644 index 0000000..9e9ec2f --- /dev/null +++ b/libtest/globals.c @@ -0,0 +1,44 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +volatile int libtest_malloc_is_custom = -1; +volatile int libtest_calloc_is_custom = -1; +volatile int libtest_realloc_is_custom = -1; +volatile int libtest_reallocarray_is_custom = -1; +volatile int libtest_valloc_is_custom = -1; +volatile int libtest_pvalloc_is_custom = -1; +volatile int libtest_memalign_is_custom = -1; +volatile int libtest_aligned_alloc_is_custom = -1; +volatile int libtest_posix_memalign_is_custom = -1; +volatile int libtest_malloc_usable_size_is_custom = -1; +volatile int libtest_free_is_custom = -1; +volatile int libtest_free_sized_is_custom = -1; +volatile int libtest_free_aligned_sized_is_custom = -1; + +struct meminfo libtest_allocs_head; +struct meminfo libtest_allocs_tail; +int libtest_allocs_list_inited = 0; +atomic_flag libtest_allocs_list_spinlock = ATOMIC_FLAG_INIT; + +int libtest_zero_on_alloc = 0; +int libtest_expect_zeroed = 0; +int libtest_malloc_accept_leakage = 1; + +_Thread_local size_t libtest_malloc_internal_usage = 0u; +_Thread_local size_t libtest_kill_malloc_tracking = 0u; + + +#else + + +CONST int +main(void) +{ + /* There isn't really anything to test here */ + return 0; +} + + +#endif diff --git a/libtest/libtest.h b/libtest/libtest.h new file mode 100644 index 0000000..3a3d963 --- /dev/null +++ b/libtest/libtest.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ + +void libtest_start_tracking(void); +void libtest_stop_tracking(void); +int libtest_check_no_leaks(void); +void libtest_force_zero_on_alloc(int); +void libtest_expect_zeroed_on_free(int); + +int libtest_have_custom_malloc(void); +int libtest_have_custom_calloc(void); +int libtest_have_custom_realloc(void); +int libtest_have_custom_reallocarray(void); +int libtest_have_custom_valloc(void); +int libtest_have_custom_pvalloc(void); +int libtest_have_custom_memalign(void); +int libtest_have_custom_aligned_alloc(void); +int libtest_have_custom_posix_memalign(void); +int libtest_have_custom_malloc_usable_size(void); +int libtest_have_custom_free(void); +int libtest_have_custom_free_sized(void); +int libtest_have_custom_free_aligned_sized(void); + +void libtest_dump_stack(const char *indent); diff --git a/libtest/libtest_alloc.c b/libtest/libtest_alloc.c new file mode 100644 index 0000000..5aa3218 --- /dev/null +++ b/libtest/libtest_alloc.c @@ -0,0 +1,208 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(WITH_BACKTRACE) && defined(__GNUC__) +# pragma GCC diagnostic ignored "-Walloca" +#endif + + +static _Thread_local int inside_mmap_anon = 0; + + +static void +meminfo_fixup(struct meminfo *meminfo) +{ + if (meminfo->alignment_type == DEFAULT_ALIGNMENT) + meminfo->requested_alignment = _Alignof(max_align_t); + else if (meminfo->alignment_type == PAGE_ALIGNMENT) + meminfo->requested_alignment = libtest_get_pagesize(); + + meminfo->actual_alignment = meminfo->requested_alignment; + meminfo->accept_leakage = !!libtest_malloc_accept_leakage; + + if (libtest_zero_on_alloc) + meminfo->initialised_on_alloc |= !meminfo->accept_leakage; +} + + +static int +add_or_enomem(size_t *augend, size_t augment) +{ + if (*augend > SIZE_MAX - augment) { + errno = ENOMEM; + return -1; + } + *augend += augment; + return 0; +} + + +static void * +mmap_anon(size_t size) +{ + void *ptr; + inside_mmap_anon = 1; + ptr = libtest_real_mmap(NULL, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED, + -1, 0); + inside_mmap_anon = 0; + return ptr == MAP_FAILED ? NULL : ptr; +} + + +void * +libtest_alloc(struct meminfo *meminfo) +{ + static _Thread_local int recursion_guard = 0; + void *base_ptr, *ret_ptr; + size_t misalignment; + size_t bookkeeping; + int saved_errno; +#ifdef WITH_BACKTRACE + size_t backtrace_realignment; + size_t i, backtrace_n; + unw_cursor_t cursor; + unw_context_t context; + unw_word_t rip; + struct partial { + uintptr_t rips[8]; + struct partial *next; + } *current, head; +#endif + + assert(!inside_mmap_anon); + + saved_errno = errno; + meminfo_fixup(meminfo); +#ifdef WITH_BACKTRACE + meminfo->accept_leakage |= libtest_malloc_internal_usage > 0; +#endif + + /* Get backtrace (have to do it now to calculate `backtrace_n` for allocation) */ +#ifdef WITH_BACKTRACE + backtrace_n = 0u; + if (!libtest_malloc_accept_leakage && !libtest_malloc_internal_usage) { + libtest_malloc_internal_usage++; + if (unw_getcontext(&context) || unw_init_local(&cursor, &context)) + goto skip_backtrace; + for (current = &head, i = 0u; unw_step(&cursor) > 0; i++) { + if (i % ELEMSOF(current->rips) == 0u) + current = current->next = alloca(sizeof(*current)); + if (unw_get_reg(&cursor, UNW_REG_IP, &rip)) + goto skip_backtrace; + current->rips[i % ELEMSOF(current->rips)] = (uintptr_t)rip; + } + backtrace_n = i; + skip_backtrace: + libtest_malloc_internal_usage--; + } +#endif + + /* Calculate required alloction size */ + meminfo->real_alloc_size = meminfo->requested_alloc_size; + if (add_or_enomem(&meminfo->real_alloc_size, sizeof(struct meminfo)) || + add_or_enomem(&meminfo->real_alloc_size, _Alignof(void *) + sizeof(void *)) || + add_or_enomem(&meminfo->real_alloc_size, meminfo->actual_alignment)) + return NULL; +#ifdef WITH_BACKTRACE + if (backtrace_n) { + misalignment = sizeof(struct meminfo) % _Alignof(struct backtrace); + backtrace_realignment = misalignment ? _Alignof(struct backtrace) - misalignment : 0u; + if (add_or_enomem(&meminfo->real_alloc_size, backtrace_realignment) || + add_or_enomem(&meminfo->real_alloc_size, offsetof(struct backtrace, trace)) || + add_or_enomem(&meminfo->real_alloc_size, backtrace_n * sizeof(Dwarf_Addr))) + return NULL; + } +#endif + bookkeeping = meminfo->real_alloc_size - meminfo->requested_alloc_size; + bookkeeping -= meminfo->actual_alignment; + + /* Allocate memory */ + base_ptr = mmap_anon(meminfo->real_alloc_size); + if (!base_ptr) + return NULL; + if (meminfo->initialised_on_alloc) + memset(base_ptr, 0, meminfo->real_alloc_size); + ret_ptr = &((char *)base_ptr)[bookkeeping]; + + /* Save backtrace */ +#ifdef WITH_BACKTRACE + if (backtrace_n) { + meminfo->backtrace = (void *)&((char *)base_ptr)[sizeof(struct meminfo) + backtrace_realignment]; + meminfo->backtrace->n = backtrace_n; + for (current = &head, i = 0u; i < backtrace_n; i++) { + if (i % ELEMSOF(current->rips) == 0u) + current = current->next; + meminfo->backtrace->trace[i] = current->rips[i % ELEMSOF(current->rips)]; + } + } else { + meminfo->backtrace = NULL; + } +#endif + + /* Fix alignment */ + misalignment = (size_t)(uintptr_t)ret_ptr % meminfo->actual_alignment; + if (misalignment) + ret_ptr = &((char *)ret_ptr)[meminfo->actual_alignment - misalignment]; + + /* Store usable size */ + meminfo->usable_alloc_size = meminfo->real_alloc_size; + meminfo->usable_alloc_size -= (size_t)((char *)ret_ptr - (char *)base_ptr); + + /* Store book-keeping */ + meminfo->usable_area = ret_ptr; + memcpy(base_ptr, meminfo, sizeof(*meminfo)); + BASE_POINTER(ret_ptr) = base_ptr; + meminfo = base_ptr; + + /* Track allocation */ + if (!libtest_kill_malloc_tracking) { + assert(!recursion_guard); + recursion_guard = 1; + SPINLOCK(libtest_allocs_list_spinlock); + if (!libtest_allocs_list_inited) { + libtest_allocs_head.prev = NULL; + libtest_allocs_head.next = &libtest_allocs_tail; + libtest_allocs_tail.prev = &libtest_allocs_head; + libtest_allocs_tail.next = NULL; + libtest_allocs_list_inited = 1; + } + libtest_allocs_tail.prev->next = meminfo; + meminfo->prev = libtest_allocs_tail.prev; + meminfo->next = &libtest_allocs_tail; + SPINUNLOCK(libtest_allocs_list_spinlock); + recursion_guard = 0; + } + + /* Optionally print out trace */ +#ifdef WITH_BACKTRACE + if (meminfo->backtrace && !libtest_malloc_internal_usage && getenv("TRACE_MALLOC")) { + libtest_malloc_internal_usage++; + fprintf(stderr, "Memory allocated: %p (alloc-size=%zu, real-size=%zu, leak-allowed=%i)\n", + ret_ptr, meminfo->requested_alloc_size, meminfo->real_alloc_size, meminfo->accept_leakage); + libtest_print_backtrace(stderr, "\tat ", 0u, NULL); + fflush(stderr); + libtest_malloc_internal_usage--; + } +#endif + + errno = saved_errno; + return ret_ptr; +} + + +#else + + +CONST int +main(void) +{ + /* Tested via alloc.c */ + return 0; +} + + +#endif diff --git a/libtest/libtest_base_pointer.c b/libtest/libtest_base_pointer.c new file mode 100644 index 0000000..4902643 --- /dev/null +++ b/libtest/libtest_base_pointer.c @@ -0,0 +1,20 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline void **libtest_base_pointer(void *); + + +#else + + +CONST int +main(void) +{ + /* This one isn't that simple to test, but it works if other tests work :) */ + return 0; +} + + +#endif diff --git a/libtest/libtest_check_no_leaks.c b/libtest/libtest_check_no_leaks.c new file mode 100644 index 0000000..15f7e58 --- /dev/null +++ b/libtest/libtest_check_no_leaks.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +extern int libtest_suppress_leak_output; +#ifndef TEST + + +int libtest_suppress_leak_output = 0; + + +static int +check_no_memory_leaks(void) +{ + struct meminfo *mem; + int no_leaks = 1; + + SPINLOCK(libtest_allocs_list_spinlock); + + if (!libtest_allocs_list_inited) + goto out; + + libtest_kill_malloc_tracking++; + libtest_malloc_internal_usage++; + for (mem = libtest_allocs_head.next; mem->next; mem = mem->next) { + if (mem->accept_leakage) + continue; + no_leaks = 0; + if (libtest_suppress_leak_output) + continue; + fprintf(stderr, "Memory leak: %p (alloc-size=%zu)\n", + mem->usable_area, mem->requested_alloc_size); +#ifdef WITH_BACKTRACE + if (mem->backtrace) + libtest_print_backtrace(stderr, "\tat ", 0u, mem->backtrace); +#endif + fflush(stderr); + } + libtest_malloc_internal_usage--; + libtest_kill_malloc_tracking--; + +out: + SPINUNLOCK(libtest_allocs_list_spinlock); + return no_leaks; +} + + +int +libtest_check_no_leaks(void) +{ + /* TODO check file descriptor leaks */ + return check_no_memory_leaks(); +} + + +#else + + +int +main(void) +{ + void *p; + + SET_UP_ALARM(); + + libtest_start_tracking(); + assert(p = malloc(1u)); + libtest_suppress_leak_output = 1; + EXPECT(!libtest_check_no_leaks()); + libtest_suppress_leak_output = 0; + free(p); + libtest_stop_tracking(); + EXPECT(libtest_check_no_leaks()); + + return 0; +} + + +#endif diff --git a/libtest/libtest_dump_stack.c b/libtest/libtest_dump_stack.c new file mode 100644 index 0000000..ca0c9c7 --- /dev/null +++ b/libtest/libtest_dump_stack.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#ifndef WITH_BACKTRACE +# if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wattributes" +# endif +CONST +#endif +void +libtest_dump_stack(const char *indent) +{ +#ifndef WITH_BACKTRACE + (void) indent; +#else + libtest_print_backtrace(stderr, indent, 1u, NULL); +#endif +} + + +#else + + +CONST int +main(void) +{ + /* How would one even test this, and what would be the point? */ + return 0; +} + + +#endif diff --git a/libtest/libtest_expect_zeroed_on_free.c b/libtest/libtest_expect_zeroed_on_free.c new file mode 100644 index 0000000..959ad07 --- /dev/null +++ b/libtest/libtest_expect_zeroed_on_free.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_expect_zeroed_on_free(int v) +{ + libtest_expect_zeroed = v; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + EXPECT(libtest_expect_zeroed == 0); + libtest_expect_zeroed_on_free(1); + EXPECT(libtest_expect_zeroed == 1); + libtest_expect_zeroed_on_free(0); + EXPECT(libtest_expect_zeroed == 0); + + return 0; +} + + +#endif diff --git a/libtest/libtest_force_zero_on_alloc.c b/libtest/libtest_force_zero_on_alloc.c new file mode 100644 index 0000000..907eabc --- /dev/null +++ b/libtest/libtest_force_zero_on_alloc.c @@ -0,0 +1,31 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_force_zero_on_alloc(int v) +{ + libtest_zero_on_alloc = v; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + EXPECT(libtest_zero_on_alloc == 0); + libtest_force_zero_on_alloc(1); + EXPECT(libtest_zero_on_alloc == 1); + libtest_force_zero_on_alloc(0); + EXPECT(libtest_zero_on_alloc == 0); + + return 0; +} + + +#endif diff --git a/libtest/libtest_free.c b/libtest/libtest_free.c new file mode 100644 index 0000000..398994b --- /dev/null +++ b/libtest/libtest_free.c @@ -0,0 +1,94 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_free(void *ptr, enum libtest_zero_check zero_checking) +{ + struct meminfo *mem; + int saved_errno = errno; + int unmap_err, memory_zeroed; + uint8_t *usable_area; + size_t i; +#ifdef WITH_BACKTRACE + static _Thread_local int inside_free = 0; +#endif + + /* free(3) can be called with NULL */ + if (!ptr) + return; + + /* Optionally print out trace */ +#ifdef WITH_BACKTRACE + if (!inside_free && getenv("PRETRACE_FREE")) { + inside_free = 1; + fprintf(stderr, "Deallocating: %p\n", ptr); + libtest_print_backtrace(stderr, "\tat ", 0u, NULL); + fflush(stderr); + inside_free = 0; + } +#endif + + /* Get the start of the allocation, which + * is where the book-keeping is located */ + mem = GET_MEMINFO(ptr); + + /* It is illegal to use free(3) on memory allocated with mmap(3)/mmap(2) */ + assert(mem->origin != FROM_MMAP_FILE); + assert(mem->origin != FROM_MMAP_ANON); + + /* Delist allocation */ + if (!libtest_kill_malloc_tracking) { + SPINLOCK(libtest_allocs_list_spinlock); + mem->prev->next = mem->next; + mem->next->prev = mem->prev; + SPINUNLOCK(libtest_allocs_list_spinlock); + } + + /* Check memory is zeroed */ + if (zero_checking && libtest_expect_zeroed && !mem->accept_leakage) { + usable_area = mem->usable_area; + memory_zeroed = 1; + for (i = 0u; i < mem->usable_alloc_size; i++) { + if (usable_area[i]) { + memory_zeroed = 0; + break; + } + } + assert(memory_zeroed); + } + + /* Optionally print out trace */ +#ifdef WITH_BACKTRACE + if (!inside_free && getenv("TRACE_MALLOC")) { + inside_free = 1; + fprintf(stderr, "Memory deallocated: %p\n (alloc-size=%zu, real-size=%zu)", + ptr, mem->requested_alloc_size, mem->real_alloc_size); + if (getenv("TRACE_FREE") && !getenv("PRETRACE_FREE")) + libtest_print_backtrace(stderr, "\tat ", 0u, NULL); + fflush(stderr); + inside_free = 0; + } +#endif + + /* Deallocate memory */ + unmap_err = libtest_real_munmap(mem, mem->real_alloc_size); + assert(!unmap_err); + + errno = saved_errno; +} + + +#else + + +CONST int +main(void) +{ + /* Tested via alloc.c */ + return 0; +} + + +#endif diff --git a/libtest/libtest_get_pagesize.c b/libtest/libtest_get_pagesize.c new file mode 100644 index 0000000..52adbdc --- /dev/null +++ b/libtest/libtest_get_pagesize.c @@ -0,0 +1,37 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +size_t +libtest_get_pagesize(void) +{ + long int r; + int saved_errno = errno; + if ((r = sysconf(_SC_PAGESIZE)) <= 0) + if ((r = sysconf(_SC_PAGE_SIZE)) <= 0) + r = 4096L; + errno = saved_errno; + return (size_t)r; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + +#ifdef __linux__ + errno = 0; + EXPECT(libtest_get_pagesize() == 4096u); + EXPECT(!errno); +#endif + + return 0; +} + + +#endif diff --git a/libtest/libtest_print_backtrace.c b/libtest/libtest_print_backtrace.c new file mode 100644 index 0000000..8fa0fd5 --- /dev/null +++ b/libtest/libtest_print_backtrace.c @@ -0,0 +1,122 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +#if defined(__linux__) +# define HAVE_LINE_INFO +#endif + + +void +libtest_print_backtrace(FILE *fp, const char *indent, size_t first, const struct backtrace *backtrace) +{ + static _Thread_local int recursion_guard = 0; + int saved_errno; + unw_word_t rip; + unw_cursor_t cursor; + unw_context_t context; + Dwarf_Addr ip; + size_t i; +#if defined(HAVE_LINE_INFO) + Dwfl_Callbacks callbacks; + char *debuginfo_path = NULL; + Dwfl *dwfl = NULL; + Dwfl_Line *line = NULL; + Dwfl_Module *module = NULL; + int lineno = 0; /* initialised for compiler happiness */ + const char *filename = NULL; + const char *funcname = NULL; +#endif + + if (recursion_guard) + return; + saved_errno = errno; + recursion_guard = 1; + libtest_malloc_internal_usage++; + + if (!backtrace) { + if (unw_getcontext(&context)) + goto out; + if (unw_init_local(&cursor, &context)) + goto out; + } + +#if defined(__linux__) + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.find_elf = &dwfl_linux_proc_find_elf; + callbacks.find_debuginfo = &dwfl_standard_find_debuginfo; + callbacks.section_address = NULL; + callbacks.debuginfo_path = &debuginfo_path; + + dwfl = dwfl_begin(&callbacks); + if (dwfl) { + if (dwfl_linux_proc_report(dwfl, getpid()) || + dwfl_report_end(dwfl, NULL, NULL)) { + dwfl_end(dwfl); + dwfl = NULL; + } + } +#endif + + for (i = 0u; backtrace ? i < backtrace->n : unw_step(&cursor) > 0; i++) { + if (backtrace) { + ip = backtrace->trace[i]; + } else { + if (unw_get_reg(&cursor, UNW_REG_IP, &rip)) + break; + ip = (Dwarf_Addr)rip; + } + if (i < first) + continue; + +#if defined(HAVE_LINE_INFO) + if (dwfl) { + module = dwfl_addrmodule(dwfl, ip); + funcname = module ? dwfl_module_addrname(module, ip) : NULL; + line = dwfl_getsrc(dwfl, ip); + if (line) { + filename = dwfl_lineinfo(line, &(Dwarf_Addr){0}, &lineno, NULL, NULL, NULL); +# ifdef USE_BASENAMES_IN_BACKTRACES + if (strrchr(filename, '/')) + filename = &strrchr(filename, '/')[1]; +# endif + } + } +#endif + +#if defined(HAVE_LINE_INFO) + fprintf(fp, "%s0x%016"PRIxPTR": %s", indent, (uintptr_t)ip, funcname ? funcname : "???"); + if (line) + fprintf(fp, " (%s:%i)\n", filename, lineno); + else + fprintf(fp, "\n"); +#else + fprintf(fp, "%s0x%016"PRIxPTR"\n", indent, (uintptr_t)ip); +#endif + } + +#if defined(HAVE_LINE_INFO) + if (dwfl) + dwfl_end(dwfl); +#endif + +out: + libtest_malloc_internal_usage--; + recursion_guard = 0; + errno = saved_errno; +} + + +#else + + +CONST int +main(void) +{ + /* How would one even test this, and what would be the point? */ + return 0; +} + + +#endif diff --git a/libtest/libtest_start_tracking.c b/libtest/libtest_start_tracking.c new file mode 100644 index 0000000..070fab9 --- /dev/null +++ b/libtest/libtest_start_tracking.c @@ -0,0 +1,29 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_start_tracking(void) +{ + libtest_malloc_accept_leakage = 0; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + EXPECT(libtest_malloc_accept_leakage == 1); + libtest_start_tracking(); + EXPECT(libtest_malloc_accept_leakage == 0); + + return 0; +} + + +#endif diff --git a/libtest/libtest_stop_tracking.c b/libtest/libtest_stop_tracking.c new file mode 100644 index 0000000..29bb2aa --- /dev/null +++ b/libtest/libtest_stop_tracking.c @@ -0,0 +1,34 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +void +libtest_stop_tracking(void) +{ + libtest_malloc_accept_leakage = 1; +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + + EXPECT(libtest_malloc_accept_leakage == 1); + + libtest_malloc_accept_leakage = 0; + + EXPECT(libtest_malloc_accept_leakage == 0); + + libtest_stop_tracking(); + EXPECT(libtest_malloc_accept_leakage == 1); + + return 0; +} + + +#endif |
