aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-05-08 22:29:35 +0200
committerMattias Andrée <m@maandree.se>2026-05-08 22:29:35 +0200
commit2d3a573977417d917c16742d8d9d8ead047d0ebc (patch)
treecaeac52856a9df0478e2bee53e5dda1f84422461
parentAdd DEFAULT_SUPPORT option and improve DEPENDENCIES (diff)
downloadlibrecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.gz
librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.bz2
librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.xz
Misc
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r--Makefile28
-rw-r--r--algorithms.h8
-rw-r--r--argon2/hash.c123
-rw-r--r--argon2/is_algorithm.c2
-rw-r--r--argon2/make_settings.c102
-rw-r--r--argon2/test_supported.c19
-rw-r--r--common.h53
-rw-r--r--config-coverage-gcc.mk2
-rw-r--r--config.mk2
-rw-r--r--librecrypt.h4
-rw-r--r--librecrypt_add_algorithm.c2
-rw-r--r--librecrypt_algorithms_.c2
-rw-r--r--librecrypt_chain_length.c2
-rw-r--r--librecrypt_check_settings_.c48
-rw-r--r--librecrypt_common_rfc4848s4_decoding_lut_.c2
-rw-r--r--librecrypt_common_rfc4848s4_encoding_lut_.c2
-rw-r--r--librecrypt_crypt.c2
-rw-r--r--librecrypt_decode.c2
-rw-r--r--librecrypt_decompose_chain.c2
-rw-r--r--librecrypt_decompose_chain1.c2
-rw-r--r--librecrypt_encode.c4
-rw-r--r--librecrypt_equal.c4
-rw-r--r--librecrypt_equal_binary.c4
-rw-r--r--librecrypt_fill_with_random_.c6
-rw-r--r--librecrypt_find_first_algorithm_.c2
-rw-r--r--librecrypt_get_encoding.c2
-rw-r--r--librecrypt_hash.c2
-rw-r--r--librecrypt_hash_.c5
-rw-r--r--librecrypt_hash_binary.c2
-rw-r--r--librecrypt_make_settings.c2
-rw-r--r--librecrypt_next_algorithm.c4
-rw-r--r--librecrypt_realise_salts.c2
-rw-r--r--librecrypt_rng_.c2
-rw-r--r--librecrypt_settings_prefix.c11
-rw-r--r--librecrypt_test_supported.c2
-rw-r--r--librecrypt_wipe.c4
-rw-r--r--librecrypt_wipe_str.c2
-rw-r--r--libtest/Makefile78
-rw-r--r--libtest/alloc.c505
-rw-r--r--libtest/alloc_have_custom.c236
-rw-r--r--libtest/common.h242
-rw-r--r--libtest/config.mk4
-rw-r--r--libtest/config_backtraces=false.mk10
-rw-r--r--libtest/config_backtraces=true.mk12
-rw-r--r--libtest/globals.c44
-rw-r--r--libtest/libtest.h23
-rw-r--r--libtest/libtest_alloc.c208
-rw-r--r--libtest/libtest_base_pointer.c20
-rw-r--r--libtest/libtest_check_no_leaks.c77
-rw-r--r--libtest/libtest_dump_stack.c34
-rw-r--r--libtest/libtest_expect_zeroed_on_free.c31
-rw-r--r--libtest/libtest_force_zero_on_alloc.c31
-rw-r--r--libtest/libtest_free.c94
-rw-r--r--libtest/libtest_get_pagesize.c37
-rw-r--r--libtest/libtest_print_backtrace.c122
-rw-r--r--libtest/libtest_start_tracking.c29
-rw-r--r--libtest/libtest_stop_tracking.c34
57 files changed, 2275 insertions, 65 deletions
diff --git a/Makefile b/Makefile
index 08bb33d..9d12bea 100644
--- a/Makefile
+++ b/Makefile
@@ -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(&params);
+ 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, &params, &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;
}
diff --git a/common.h b/common.h
index 53f4ff4..bbb4ede 100644
--- a/common.h
+++ b/common.h
@@ -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
diff --git a/config.mk b/config.mk
index 14bd38d..36994f3 100644
--- a/config.mk
+++ b/config.mk
@@ -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