Merge branch 'ds/trace2-tolerate-failed-timestamp' into seen

Functions like `xstrfmt()` and `xcalloc()` have been banned from use
in the trace2 API codebase to prevent calls to `die()` which lead to
unwanted process exits and recursion when memory allocation fails.

* ds/trace2-tolerate-failed-timestamp:
  trace2: remove use of xcalloc()
  trace2: remove use of ALLOC_GROW()
  trace2: remove use of xstrfmt()
  trace2: remove use of ALLOC_ARRAY()
  trace2: remove use of xstrdup()
  trace2: tolerate failed timestamp formatting
  banned-die: create header for banning of functions
seen
Junio C Hamano 2026-08-31 13:53:25 -07:00
commit 075cd7bce4
16 changed files with 250 additions and 31 deletions

32
banned-die.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef BANNED_DIE_H
#define BANNED_DIE_H

#include "banned.h"

/*
* This header lists functions that must not be used by low-level APIs
* because they can cause Git to terminate.
*/

#undef die
#define die BANNED(die)

#undef xsnprintf
#define xsnprintf(...) BANNED(xsnprintf)

#undef xstrdup
#define xstrdup(str) BANNED(xstrdup)

#undef xcalloc
#define xcalloc(nmemb, size) BANNED(xcalloc)

#undef xstrfmt
#define xstrfmt(...) BANNED(xstrfmt)

#undef ALLOC_ARRAY
#define ALLOC_ARRAY(x, alloc) BANNED(ALLOC_ARRAY)

#undef ALLOC_GROW
#define ALLOC_GROW(x, nr, alloc) BANNED(ALLOC_GROW)

#endif /* BANNED_DIE_H */

View File

@ -332,7 +332,8 @@ test_expect_success 'unsafe URLs are redacted by default in cmd_start events' '

GIT_TRACE2_EVENT="$(pwd)/trace.event" \
test-tool trace2 300redact_start git clone https://user:pwd@example.com/ clone2 &&
test_grep ! user:pwd trace.event
test_grep ! user:pwd trace.event &&
test_grep "user:<REDACTED>@example.com/" trace.event
'

test_expect_success 'unsafe URLs are redacted by default in child_start events' '
@ -341,7 +342,8 @@ test_expect_success 'unsafe URLs are redacted by default in child_start events'

GIT_TRACE2_EVENT="$(pwd)/trace.event" \
test-tool trace2 301redact_child_start git clone https://user:pwd@example.com/ clone2 &&
test_grep ! user:pwd trace.event
test_grep ! user:pwd trace.event &&
test_grep "user:<REDACTED>@example.com/" trace.event
'

test_expect_success 'unsafe URLs are redacted by default in exec events' '
@ -350,7 +352,8 @@ test_expect_success 'unsafe URLs are redacted by default in exec events' '

GIT_TRACE2_EVENT="$(pwd)/trace.event" \
test-tool trace2 302redact_exec git clone https://user:pwd@example.com/ clone2 &&
test_grep ! user:pwd trace.event
test_grep ! user:pwd trace.event &&
test_grep "user:<REDACTED>@example.com/" trace.event
'

test_expect_success 'unsafe URLs are redacted by default in def_param events' '
@ -359,7 +362,8 @@ test_expect_success 'unsafe URLs are redacted by default in def_param events' '

GIT_TRACE2_EVENT="$(pwd)/trace.event" \
test-tool trace2 303redact_def_param url https://user:pwd@example.com/ &&
test_grep ! user:pwd trace.event
test_grep ! user:pwd trace.event &&
test_grep "user:<REDACTED>@example.com/" trace.event
'

test_done

View File

@ -17,6 +17,8 @@
#include "trace2/tr2_tgt.h"
#include "trace2/tr2_tls.h"
#include "trace2/tr2_tmr.h"
/* banned-die must be last. */
#include "banned-die.h"

static int trace2_enabled;
static int trace2_redact = 1;
@ -259,7 +261,10 @@ int trace2_is_enabled(void)
static const char *redact_arg(const char *arg)
{
const char *p, *colon;
const char *redact = ":<REDACTED>";
char *redacted;
size_t at;
size_t prefix_len, suffix_len, redacted_len, redact_len;

if (!trace2_redact ||
(!skip_prefix(arg, "https://", &p) &&
@ -274,7 +279,25 @@ static const char *redact_arg(const char *arg)
if (!colon)
return arg;

return xstrfmt("%.*s:<REDACTED>%s", (int)(colon - arg), arg, p + at);
redact_len = strlen(redact);
prefix_len = colon - arg;
suffix_len = strlen(p + at);

if (unsigned_add_overflows(prefix_len, suffix_len) ||
unsigned_add_overflows(prefix_len + suffix_len, redact_len) ||
unsigned_add_overflows(prefix_len + suffix_len + redact_len, 1))
return NULL;

redacted_len = prefix_len + suffix_len + redact_len + 1;

redacted = malloc(redacted_len);
if (!redacted)
return NULL;

memcpy(redacted, arg, prefix_len);
memcpy(redacted + prefix_len, redact, redact_len);
memcpy(redacted + prefix_len + redact_len, p + at, suffix_len + 1);
return redacted;
}

/*
@ -299,11 +322,17 @@ static const char **redact_argv(const char **argv)

if (!argv[i])
return argv;
if (!redacted)
return NULL;

for (j = 0; argv[j]; j++)
; /* keep counting */

ALLOC_ARRAY(ret, j + 1);
ret = calloc(j + 1, sizeof(*ret));
if (!ret) {
free((char *)redacted);
return NULL;
}
ret[j] = NULL;

for (j = 0; j < i; j++)
@ -311,7 +340,14 @@ static const char **redact_argv(const char **argv)
ret[i] = redacted;
for (++i; argv[i]; i++) {
redacted = redact_arg(argv[i]);
ret[i] = redacted ? redacted : argv[i];
if (!redacted) {
for (j = 0; j < i; j++)
if (ret[j] != argv[j])
free((void *)ret[j]);
free(ret);
return NULL;
}
ret[i] = redacted;
}

return ret;
@ -344,6 +380,8 @@ void trace2_cmd_start_fl(const char *file, int line, const char **argv)
us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);

redacted = redact_argv(argv);
if (!redacted)
return;

for_each_wanted_builtin (j, tgt_j)
if (tgt_j->pfn_start_fl)
@ -512,6 +550,7 @@ void trace2_child_start_fl(const char *file, int line,
uint64_t us_now;
uint64_t us_elapsed_absolute;
const char **orig_argv = cmd->args.v;
const char **redacted;

if (!trace2_enabled)
return;
@ -529,7 +568,10 @@ void trace2_child_start_fl(const char *file, int line,
* temporarily replace the original argv (inside the `strvec`)
* with a possibly redacted version.
*/
cmd->args.v = redact_argv(orig_argv);
redacted = redact_argv(orig_argv);
if (!redacted)
return;
cmd->args.v = redacted;

for_each_wanted_builtin (j, tgt_j)
if (tgt_j->pfn_child_start_fl)
@ -621,6 +663,8 @@ int trace2_exec_fl(const char *file, int line, const char *exe,
exec_id = tr2tls_locked_increment(&tr2_next_exec_id);

redacted = redact_argv(argv);
if (!redacted)
return exec_id;

for_each_wanted_builtin (j, tgt_j)
if (tgt_j->pfn_exec_fl)

View File

@ -7,6 +7,8 @@
#include "trace2/tr2_cfg.h"
#include "trace2/tr2_sysenv.h"
#include "wildmatch.h"
/* banned-die must be last. */
#include "banned-die.h"

static struct string_list tr2_cfg_patterns = STRING_LIST_INIT_DUP;
static int tr2_cfg_loaded;

View File

@ -1,6 +1,8 @@
#include "git-compat-util.h"
#include "strbuf.h"
#include "trace2/tr2_cmd_name.h"
/* banned-die must be last. */
#include "banned-die.h"

#define TR2_ENVVAR_PARENT_NAME "GIT_TRACE2_PARENT_NAME"


View File

@ -2,6 +2,8 @@
#include "trace2/tr2_tgt.h"
#include "trace2/tr2_tls.h"
#include "trace2/tr2_ctr.h"
/* banned-die must be last. */
#include "banned-die.h"

/*
* A global counter block to aggregate values from the partial sums
@ -53,7 +55,11 @@ static struct tr2_counter_metadata tr2_counter_metadata[TRACE2_NUMBER_OF_COUNTER
void tr2_counter_increment(enum trace2_counter_id cid, uint64_t value)
{
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
struct tr2_counter *c = &ctx->counter_block.counter[cid];
struct tr2_counter *c;

if (tr2tls_is_fallback(ctx))
return;
c = &ctx->counter_block.counter[cid];

c->value += value;

@ -67,6 +73,8 @@ void tr2_update_final_counters(void)
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
enum trace2_counter_id cid;

if (tr2tls_is_fallback(ctx))
return;
if (!ctx->used_any_counter)
return;

@ -88,6 +96,8 @@ void tr2_emit_per_thread_counters(tr2_tgt_evt_counter_t *fn_apply)
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
enum trace2_counter_id cid;

if (tr2tls_is_fallback(ctx))
return;
if (!ctx->used_any_per_thread_counter)
return;


View File

@ -5,6 +5,8 @@
#include "trace2/tr2_dst.h"
#include "trace2/tr2_sid.h"
#include "trace2/tr2_sysenv.h"
/* banned-die must be last. */
#include "banned-die.h"

/*
* How many attempts we will make at creating an automatically-named trace file.

View File

@ -3,6 +3,8 @@
#include "strbuf.h"
#include "trace2/tr2_tbuf.h"
#include "trace2/tr2_sid.h"
/* banned-die must be last. */
#include "banned-die.h"

#define TR2_ENVVAR_PARENT_SID "GIT_TRACE2_PARENT_SID"


View File

@ -4,6 +4,8 @@
#include "config.h"
#include "dir.h"
#include "tr2_sysenv.h"
/* banned-die must be last. */
#include "banned-die.h"

/*
* Each entry represents a trace2 setting.
@ -73,7 +75,9 @@ static int tr2_sysenv_cb(const char *key, const char *value,
if (!value)
return config_error_nonbool(key);
free(tr2_sysenv_settings[k].value);
tr2_sysenv_settings[k].value = xstrdup(value);
tr2_sysenv_settings[k].value = strdup(value);
if (!tr2_sysenv_settings[k].value)
return 0;
return 0;
}
}
@ -109,7 +113,7 @@ const char *tr2_sysenv_get(enum tr2_sysenv_variable var)
const char *v = getenv(tr2_sysenv_settings[var].env_var_name);
if (v && *v) {
free(tr2_sysenv_settings[var].value);
tr2_sysenv_settings[var].value = xstrdup(v);
tr2_sysenv_settings[var].value = strdup(v);
}
tr2_sysenv_settings[var].getenv_called = 1;
}

View File

@ -1,47 +1,68 @@
#include "git-compat-util.h"
#include "tr2_tbuf.h"
/* banned-die must be last. */
#include "banned-die.h"

void tr2_tbuf_local_time(struct tr2_tbuf *tb)
{
struct timeval tv;
struct tm tm;
struct timeval tv = { 0 };
struct tm tm = { 0 };
time_t secs;
int len;

gettimeofday(&tv, NULL);
secs = tv.tv_sec;
localtime_r(&secs, &tm);

xsnprintf(tb->buf, sizeof(tb->buf), "%02d:%02d:%02d.%06ld", tm.tm_hour,
tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
len = snprintf(tb->buf, sizeof(tb->buf), "%02d:%02d:%02d.%06ld",
tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);

if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
const char *blank = "00:00:00.000000";
strlcpy(tb->buf, blank, sizeof(tb->buf));
}
}

void tr2_tbuf_utc_datetime_extended(struct tr2_tbuf *tb)
{
struct timeval tv;
struct tm tm;
struct timeval tv = { 0 };
struct tm tm = { 0 };
time_t secs;
int len;

gettimeofday(&tv, NULL);
secs = tv.tv_sec;
gmtime_r(&secs, &tm);

xsnprintf(tb->buf, sizeof(tb->buf),
"%4d-%02d-%02dT%02d:%02d:%02d.%06ldZ", tm.tm_year + 1900,
tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
(long)tv.tv_usec);
len = snprintf(tb->buf, sizeof(tb->buf),
"%4d-%02d-%02dT%02d:%02d:%02d.%06ldZ",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);

if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
const char *blank = "1900-00-00T00:00:00.000000Z";
strlcpy(tb->buf, blank, sizeof(tb->buf));
}
}

void tr2_tbuf_utc_datetime(struct tr2_tbuf *tb)
{
struct timeval tv;
struct tm tm;
struct timeval tv = { 0 };
struct tm tm = { 0 };
time_t secs;
int len;

gettimeofday(&tv, NULL);
secs = tv.tv_sec;
gmtime_r(&secs, &tm);

xsnprintf(tb->buf, sizeof(tb->buf), "%4d%02d%02dT%02d%02d%02d.%06ldZ",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
len = snprintf(tb->buf, sizeof(tb->buf),
"%4d%02d%02dT%02d%02d%02d.%06ldZ",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);

if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
const char *blank = "19000000T000000.000000Z";
strlcpy(tb->buf, blank, sizeof(tb->buf));
}
}

View File

@ -13,6 +13,8 @@
#include "trace2/tr2_tgt.h"
#include "trace2/tr2_tls.h"
#include "trace2/tr2_tmr.h"
/* banned-die must be last. */
#include "banned-die.h"

static struct tr2_dst tr2dst_event = {
.sysenv_var = TR2_SYSENV_EVENT,

View File

@ -11,6 +11,8 @@
#include "trace2/tr2_tgt.h"
#include "trace2/tr2_tls.h"
#include "trace2/tr2_tmr.h"
/* banned-die must be last. */
#include "banned-die.h"

static struct tr2_dst tr2dst_normal = {
.sysenv_var = TR2_SYSENV_NORMAL,

View File

@ -14,6 +14,8 @@
#include "trace2/tr2_tgt.h"
#include "trace2/tr2_tls.h"
#include "trace2/tr2_tmr.h"
/* banned-die must be last. */
#include "banned-die.h"

static struct tr2_dst tr2dst_perf = {
.sysenv_var = TR2_SYSENV_PERF,

View File

@ -3,6 +3,8 @@
#include "thread-utils.h"
#include "trace.h"
#include "trace2/tr2_tls.h"
/* banned-die must be last. */
#include "banned-die.h"

/*
* Initialize size of the thread stack for nested regions.
@ -12,6 +14,9 @@
#define TR2_REGION_NESTING_INITIAL_SIZE (100)

static struct tr2tls_thread_ctx *tr2tls_thread_main;
static struct tr2tls_thread_ctx tr2tls_thread_fallback = {
.thread_name = "unknown",
};
static uint64_t tr2tls_us_start_process;

static pthread_mutex_t tr2tls_mutex;
@ -36,16 +41,23 @@ void tr2tls_start_process_clock(void)
struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_base_name,
uint64_t us_thread_start)
{
struct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));
struct tr2tls_thread_ctx *ctx = calloc(1, sizeof(*ctx));
struct strbuf buf = STRBUF_INIT;

if (!ctx)
goto fallback;

/*
* Implicitly "tr2tls_push_self()" to capture the thread's start
* time in array_us_start[0]. For the main thread this gives us the
* application run time.
*/
ctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;
ctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));
ctx->array_us_start = calloc(ctx->alloc, sizeof(uint64_t));
if (!ctx->array_us_start) {
free(ctx);
goto fallback;
}
ctx->array_us_start[ctx->nr_open_regions++] = us_thread_start;

ctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);
@ -61,6 +73,10 @@ struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_base_name,
pthread_setspecific(tr2tls_key, ctx);

return ctx;

fallback:
pthread_setspecific(tr2tls_key, &tr2tls_thread_fallback);
return &tr2tls_thread_fallback;
}

struct tr2tls_thread_ctx *tr2tls_get_self(void)
@ -83,6 +99,11 @@ struct tr2tls_thread_ctx *tr2tls_get_self(void)
return ctx;
}

int tr2tls_is_fallback(const struct tr2tls_thread_ctx *ctx)
{
return ctx == &tr2tls_thread_fallback;
}

int tr2tls_is_main_thread(void)
{
if (!HAVE_THREADS)
@ -99,6 +120,9 @@ void tr2tls_unset_self(void)

pthread_setspecific(tr2tls_key, NULL);

if (tr2tls_is_fallback(ctx))
return;

free((char *)ctx->thread_name);
free(ctx->array_us_start);
free(ctx);
@ -107,8 +131,36 @@ void tr2tls_unset_self(void)
void tr2tls_push_self(uint64_t us_now)
{
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
uint64_t *new_array;
size_t new_alloc;

if (tr2tls_is_fallback(ctx))
return;

if (ctx->nr_skipped_regions) {
ctx->nr_skipped_regions++;
return;
}

if (ctx->nr_open_regions >= ctx->alloc) {
if (ctx->alloc >
SIZE_MAX / (2 * sizeof(*ctx->array_us_start))) {
ctx->nr_skipped_regions++;
return;
}
new_alloc = ctx->alloc * 2;

new_array = realloc(ctx->array_us_start,
new_alloc * sizeof(*ctx->array_us_start));
if (!new_array) {
ctx->nr_skipped_regions++;
return;
}

ctx->array_us_start = new_array;
ctx->alloc = new_alloc;
}

ALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);
ctx->array_us_start[ctx->nr_open_regions++] = us_now;
}

@ -116,6 +168,14 @@ void tr2tls_pop_self(void)
{
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();

if (tr2tls_is_fallback(ctx))
return;

if (ctx->nr_skipped_regions) {
ctx->nr_skipped_regions--;
return;
}

if (!ctx->nr_open_regions)
BUG("no open regions in thread '%s'", ctx->thread_name);

@ -126,6 +186,9 @@ void tr2tls_pop_unwind_self(void)
{
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();

if (tr2tls_is_fallback(ctx))
return;

while (ctx->nr_open_regions > 1)
tr2tls_pop_self();
}
@ -136,6 +199,10 @@ uint64_t tr2tls_region_elasped_self(uint64_t us)
uint64_t us_start;

ctx = tr2tls_get_self();
if (tr2tls_is_fallback(ctx))
return 0;
if (ctx->nr_skipped_regions)
return 0;
if (!ctx->nr_open_regions)
return 0;

@ -155,6 +222,10 @@ uint64_t tr2tls_absolute_elapsed(uint64_t us)
static void tr2tls_key_destructor(void *payload)
{
struct tr2tls_thread_ctx *ctx = payload;

if (tr2tls_is_fallback(ctx))
return;

free((char *)ctx->thread_name);
free(ctx->array_us_start);
free(ctx);

View File

@ -20,6 +20,7 @@ struct tr2tls_thread_ctx {
uint64_t *array_us_start;
size_t alloc;
size_t nr_open_regions; /* plays role of "nr" in ALLOC_GROW */
size_t nr_skipped_regions;
int thread_id;
struct tr2_timer_block timer_block;
struct tr2_counter_block counter_block;
@ -53,6 +54,12 @@ struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_base_name,
*/
struct tr2tls_thread_ctx *tr2tls_get_self(void);

/*
* Return true if the context is the non-allocating fallback used after an
* allocation failure. Callers must not modify a fallback context.
*/
int tr2tls_is_fallback(const struct tr2tls_thread_ctx *ctx);

/*
* return true if the current thread is the main thread.
*/

View File

@ -3,6 +3,8 @@
#include "trace2/tr2_tls.h"
#include "trace2/tr2_tmr.h"
#include "trace.h"
/* banned-die must be last. */
#include "banned-die.h"

#define MY_MAX(a, b) ((a) > (b) ? (a) : (b))
#define MY_MIN(a, b) ((a) < (b) ? (a) : (b))
@ -37,8 +39,11 @@ static struct tr2_timer_metadata tr2_timer_metadata[TRACE2_NUMBER_OF_TIMERS] = {
void tr2_start_timer(enum trace2_timer_id tid)
{
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
struct tr2_timer *t = &ctx->timer_block.timer[tid];
struct tr2_timer *t;

if (tr2tls_is_fallback(ctx))
return;
t = &ctx->timer_block.timer[tid];
t->recursion_count++;
if (t->recursion_count > 1)
return; /* ignore recursive starts */
@ -49,10 +54,13 @@ void tr2_start_timer(enum trace2_timer_id tid)
void tr2_stop_timer(enum trace2_timer_id tid)
{
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
struct tr2_timer *t = &ctx->timer_block.timer[tid];
struct tr2_timer *t;
uint64_t ns_now;
uint64_t ns_interval;

if (tr2tls_is_fallback(ctx))
return;
t = &ctx->timer_block.timer[tid];
assert(t->recursion_count > 0);

t->recursion_count--;
@ -90,6 +98,8 @@ void tr2_update_final_timers(void)
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
enum trace2_timer_id tid;

if (tr2tls_is_fallback(ctx))
return;
if (!ctx->used_any_timer)
return;

@ -136,6 +146,8 @@ void tr2_emit_per_thread_timers(tr2_tgt_evt_timer_t *fn_apply)
struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
enum trace2_timer_id tid;

if (tr2tls_is_fallback(ctx))
return;
if (!ctx->used_any_per_thread_timer)
return;