Skip to content

Commit 7e679a4

Browse files
committed
Merge branch 'jh/trace2-redact-auth' into next
trace2 streams used to record the URLs that potentially embed authentication material, which has been corrected. * jh/trace2-redact-auth: t0212: test URL redacting in EVENT format t0211: test URL redacting in PERF format trace2: redact passwords from https:// URLs by default trace2: fix signature of trace2_def_param() macro
2 parents ab43a54 + 16fa3ee commit 7e679a4

File tree

6 files changed

+253
-7
lines changed

6 files changed

+253
-7
lines changed

t/helper/test-trace2.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,56 @@ static int ut_201counter(int argc, const char **argv)
412412
return 0;
413413
}
414414

415+
static int ut_300redact_start(int argc, const char **argv)
416+
{
417+
if (!argc)
418+
die("expect <argv...>");
419+
420+
trace2_cmd_start(argv);
421+
422+
return 0;
423+
}
424+
425+
static int ut_301redact_child_start(int argc, const char **argv)
426+
{
427+
struct child_process cmd = CHILD_PROCESS_INIT;
428+
int k;
429+
430+
if (!argc)
431+
die("expect <argv...>");
432+
433+
for (k = 0; argv[k]; k++)
434+
strvec_push(&cmd.args, argv[k]);
435+
436+
trace2_child_start(&cmd);
437+
438+
strvec_clear(&cmd.args);
439+
440+
return 0;
441+
}
442+
443+
static int ut_302redact_exec(int argc, const char **argv)
444+
{
445+
if (!argc)
446+
die("expect <exe> <argv...>");
447+
448+
trace2_exec(argv[0], &argv[1]);
449+
450+
return 0;
451+
}
452+
453+
static int ut_303redact_def_param(int argc, const char **argv)
454+
{
455+
struct key_value_info kvi = KVI_INIT;
456+
457+
if (argc < 2)
458+
die("expect <key> <value>");
459+
460+
trace2_def_param(argv[0], argv[1], &kvi);
461+
462+
return 0;
463+
}
464+
415465
/*
416466
* Usage:
417467
* test-tool trace2 <ut_name_1> <ut_usage_1>
@@ -438,6 +488,11 @@ static struct unit_test ut_table[] = {
438488

439489
{ ut_200counter, "200counter", "<v1> [<v2> [<v3> [...]]]" },
440490
{ ut_201counter, "201counter", "<v1> <v2> <threads>" },
491+
492+
{ ut_300redact_start, "300redact_start", "<argv...>" },
493+
{ ut_301redact_child_start, "301redact_child_start", "<argv...>" },
494+
{ ut_302redact_exec, "302redact_exec", "<exe> <argv...>" },
495+
{ ut_303redact_def_param, "303redact_def_param", "<key> <value>" },
441496
};
442497
/* clang-format on */
443498

t/t0210-trace2-normal.sh

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
test_description='test trace2 facility (normal target)'
44

5-
TEST_PASSES_SANITIZE_LEAK=true
5+
TEST_PASSES_SANITIZE_LEAK=false
66
. ./test-lib.sh
77

88
# Turn off any inherited trace2 settings for this test.
@@ -283,4 +283,22 @@ test_expect_success 'using global config with include' '
283283
test_cmp expect actual
284284
'
285285

286+
test_expect_success 'unsafe URLs are redacted by default' '
287+
test_when_finished \
288+
"rm -r trace.normal unredacted.normal clone clone2" &&
289+
290+
test_config_global \
291+
"url.$(pwd).insteadOf" https://user:pwd@example.com/ &&
292+
test_config_global trace2.configParams "core.*,remote.*.url" &&
293+
294+
GIT_TRACE2="$(pwd)/trace.normal" \
295+
git clone https://user:pwd@example.com/ clone &&
296+
! grep user:pwd trace.normal &&
297+
298+
GIT_TRACE2_REDACT=0 GIT_TRACE2="$(pwd)/unredacted.normal" \
299+
git clone https://user:pwd@example.com/ clone2 &&
300+
grep "start .* clone https://user:pwd@example.com" unredacted.normal &&
301+
grep "remote.origin.url=https://user:pwd@example.com" unredacted.normal
302+
'
303+
286304
test_done

t/t0211-trace2-perf.sh

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
test_description='test trace2 facility (perf target)'
44

5-
TEST_PASSES_SANITIZE_LEAK=true
5+
TEST_PASSES_SANITIZE_LEAK=false
66
. ./test-lib.sh
77

88
# Turn off any inherited trace2 settings for this test.
@@ -268,4 +268,23 @@ test_expect_success PTHREADS 'global counter test/test2' '
268268
have_counter_event "main" "counter" "test" "test2" 60 actual
269269
'
270270

271+
test_expect_success 'unsafe URLs are redacted by default' '
272+
test_when_finished \
273+
"rm -r actual trace.perf unredacted.perf clone clone2" &&
274+
275+
test_config_global \
276+
"url.$(pwd).insteadOf" https://user:pwd@example.com/ &&
277+
test_config_global trace2.configParams "core.*,remote.*.url" &&
278+
279+
GIT_TRACE2_PERF="$(pwd)/trace.perf" \
280+
git clone https://user:pwd@example.com/ clone &&
281+
! grep user:pwd trace.perf &&
282+
283+
GIT_TRACE2_REDACT=0 GIT_TRACE2_PERF="$(pwd)/unredacted.perf" \
284+
git clone https://user:pwd@example.com/ clone2 &&
285+
perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <unredacted.perf >actual &&
286+
grep "d0|main|start|.* clone https://user:pwd@example.com" actual &&
287+
grep "d0|main|def_param|.*|remote.origin.url:https://user:pwd@example.com" actual
288+
'
289+
271290
test_done

t/t0212-trace2-event.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,44 @@ test_expect_success 'discard traces when there are too many files' '
323323
head -n2 trace_target_dir/git-trace2-discard | tail -n1 | grep \"event\":\"too_many_files\"
324324
'
325325

326+
# In the following "...redact..." tests, skip testing the GIT_TRACE2_REDACT=0
327+
# case because we would need to exactly model the full JSON event stream like
328+
# we did in the basic tests above and I do not think it is worth it.
329+
330+
test_expect_success 'unsafe URLs are redacted by default in cmd_start events' '
331+
test_when_finished \
332+
"rm -r trace.event" &&
333+
334+
GIT_TRACE2_EVENT="$(pwd)/trace.event" \
335+
test-tool trace2 300redact_start git clone https://user:pwd@example.com/ clone2 &&
336+
! grep user:pwd trace.event
337+
'
338+
339+
test_expect_success 'unsafe URLs are redacted by default in child_start events' '
340+
test_when_finished \
341+
"rm -r trace.event" &&
342+
343+
GIT_TRACE2_EVENT="$(pwd)/trace.event" \
344+
test-tool trace2 301redact_child_start git clone https://user:pwd@example.com/ clone2 &&
345+
! grep user:pwd trace.event
346+
'
347+
348+
test_expect_success 'unsafe URLs are redacted by default in exec events' '
349+
test_when_finished \
350+
"rm -r trace.event" &&
351+
352+
GIT_TRACE2_EVENT="$(pwd)/trace.event" \
353+
test-tool trace2 302redact_exec git clone https://user:pwd@example.com/ clone2 &&
354+
! grep user:pwd trace.event
355+
'
356+
357+
test_expect_success 'unsafe URLs are redacted by default in def_param events' '
358+
test_when_finished \
359+
"rm -r trace.event" &&
360+
361+
GIT_TRACE2_EVENT="$(pwd)/trace.event" \
362+
test-tool trace2 303redact_def_param url https://user:pwd@example.com/ &&
363+
! grep user:pwd trace.event
364+
'
365+
326366
test_done

trace2.c

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "trace2/tr2_tmr.h"
2121

2222
static int trace2_enabled;
23+
static int trace2_redact = 1;
2324

2425
static int tr2_next_child_id; /* modify under lock */
2526
static int tr2_next_exec_id; /* modify under lock */
@@ -227,6 +228,8 @@ void trace2_initialize_fl(const char *file, int line)
227228
if (!tr2_tgt_want_builtins())
228229
return;
229230
trace2_enabled = 1;
231+
if (!git_env_bool("GIT_TRACE2_REDACT", 1))
232+
trace2_redact = 0;
230233

231234
tr2_sid_get();
232235

@@ -247,23 +250,108 @@ int trace2_is_enabled(void)
247250
return trace2_enabled;
248251
}
249252

253+
/*
254+
* Redacts an argument, i.e. ensures that no password in
255+
* https://user:password@host/-style URLs is logged.
256+
*
257+
* Returns the original if nothing needed to be redacted.
258+
* Returns a pointer that needs to be `free()`d otherwise.
259+
*/
260+
static const char *redact_arg(const char *arg)
261+
{
262+
const char *p, *colon;
263+
size_t at;
264+
265+
if (!trace2_redact ||
266+
(!skip_prefix(arg, "https://", &p) &&
267+
!skip_prefix(arg, "http://", &p)))
268+
return arg;
269+
270+
at = strcspn(p, "@/");
271+
if (p[at] != '@')
272+
return arg;
273+
274+
colon = memchr(p, ':', at);
275+
if (!colon)
276+
return arg;
277+
278+
return xstrfmt("%.*s:<REDACTED>%s", (int)(colon - arg), arg, p + at);
279+
}
280+
281+
/*
282+
* Redacts arguments in an argument list.
283+
*
284+
* Returns the original if nothing needed to be redacted.
285+
* Otherwise, returns a new array that needs to be released
286+
* via `free_redacted_argv()`.
287+
*/
288+
static const char **redact_argv(const char **argv)
289+
{
290+
int i, j;
291+
const char *redacted = NULL;
292+
const char **ret;
293+
294+
if (!trace2_redact)
295+
return argv;
296+
297+
for (i = 0; argv[i]; i++)
298+
if ((redacted = redact_arg(argv[i])) != argv[i])
299+
break;
300+
301+
if (!argv[i])
302+
return argv;
303+
304+
for (j = 0; argv[j]; j++)
305+
; /* keep counting */
306+
307+
ALLOC_ARRAY(ret, j + 1);
308+
ret[j] = NULL;
309+
310+
for (j = 0; j < i; j++)
311+
ret[j] = argv[j];
312+
ret[i] = redacted;
313+
for (++i; argv[i]; i++) {
314+
redacted = redact_arg(argv[i]);
315+
ret[i] = redacted ? redacted : argv[i];
316+
}
317+
318+
return ret;
319+
}
320+
321+
static void free_redacted_argv(const char **redacted, const char **argv)
322+
{
323+
int i;
324+
325+
if (redacted != argv) {
326+
for (i = 0; argv[i]; i++)
327+
if (redacted[i] != argv[i])
328+
free((void *)redacted[i]);
329+
free((void *)redacted);
330+
}
331+
}
332+
250333
void trace2_cmd_start_fl(const char *file, int line, const char **argv)
251334
{
252335
struct tr2_tgt *tgt_j;
253336
int j;
254337
uint64_t us_now;
255338
uint64_t us_elapsed_absolute;
339+
const char **redacted;
256340

257341
if (!trace2_enabled)
258342
return;
259343

260344
us_now = getnanotime() / 1000;
261345
us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
262346

347+
redacted = redact_argv(argv);
348+
263349
for_each_wanted_builtin (j, tgt_j)
264350
if (tgt_j->pfn_start_fl)
265351
tgt_j->pfn_start_fl(file, line, us_elapsed_absolute,
266-
argv);
352+
redacted);
353+
354+
free_redacted_argv(redacted, argv);
267355
}
268356

269357
void trace2_cmd_exit_fl(const char *file, int line, int code)
@@ -409,6 +497,7 @@ void trace2_child_start_fl(const char *file, int line,
409497
int j;
410498
uint64_t us_now;
411499
uint64_t us_elapsed_absolute;
500+
const char **orig_argv = cmd->args.v;
412501

413502
if (!trace2_enabled)
414503
return;
@@ -419,10 +508,24 @@ void trace2_child_start_fl(const char *file, int line,
419508
cmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);
420509
cmd->trace2_child_us_start = us_now;
421510

511+
/*
512+
* The `pfn_child_start_fl` API takes a `struct child_process`
513+
* rather than a simple `argv` for the child because some
514+
* targets make use of the additional context bits/values. So
515+
* temporarily replace the original argv (inside the `strvec`)
516+
* with a possibly redacted version.
517+
*/
518+
cmd->args.v = redact_argv(orig_argv);
519+
422520
for_each_wanted_builtin (j, tgt_j)
423521
if (tgt_j->pfn_child_start_fl)
424522
tgt_j->pfn_child_start_fl(file, line,
425523
us_elapsed_absolute, cmd);
524+
525+
if (cmd->args.v != orig_argv) {
526+
free_redacted_argv(cmd->args.v, orig_argv);
527+
cmd->args.v = orig_argv;
528+
}
426529
}
427530

428531
void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
@@ -493,6 +596,7 @@ int trace2_exec_fl(const char *file, int line, const char *exe,
493596
int exec_id;
494597
uint64_t us_now;
495598
uint64_t us_elapsed_absolute;
599+
const char **redacted;
496600

497601
if (!trace2_enabled)
498602
return -1;
@@ -502,10 +606,14 @@ int trace2_exec_fl(const char *file, int line, const char *exe,
502606

503607
exec_id = tr2tls_locked_increment(&tr2_next_exec_id);
504608

609+
redacted = redact_argv(argv);
610+
505611
for_each_wanted_builtin (j, tgt_j)
506612
if (tgt_j->pfn_exec_fl)
507613
tgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,
508-
exec_id, exe, argv);
614+
exec_id, exe, redacted);
615+
616+
free_redacted_argv(redacted, argv);
509617

510618
return exec_id;
511619
}
@@ -637,13 +745,19 @@ void trace2_def_param_fl(const char *file, int line, const char *param,
637745
{
638746
struct tr2_tgt *tgt_j;
639747
int j;
748+
const char *redacted;
640749

641750
if (!trace2_enabled)
642751
return;
643752

753+
redacted = redact_arg(value);
754+
644755
for_each_wanted_builtin (j, tgt_j)
645756
if (tgt_j->pfn_param_fl)
646-
tgt_j->pfn_param_fl(file, line, param, value, kvi);
757+
tgt_j->pfn_param_fl(file, line, param, redacted, kvi);
758+
759+
if (redacted != value)
760+
free((void *)redacted);
647761
}
648762

649763
void trace2_def_repo_fl(const char *file, int line, struct repository *repo)

trace2.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,8 @@ struct key_value_info;
337337
void trace2_def_param_fl(const char *file, int line, const char *param,
338338
const char *value, const struct key_value_info *kvi);
339339

340-
#define trace2_def_param(param, value) \
341-
trace2_def_param_fl(__FILE__, __LINE__, (param), (value))
340+
#define trace2_def_param(param, value, kvi) \
341+
trace2_def_param_fl(__FILE__, __LINE__, (param), (value), (kvi))
342342

343343
/*
344344
* Tell trace2 about a newly instantiated repo object and assign

0 commit comments

Comments
 (0)