git-prompt.sh: don't put unsanitized branch names in $PS1
Both bash and zsh subject the value of PS1 to parameter expansion, command substitution, and arithmetic expansion. Rather than include the raw, unescaped branch name in PS1 when running in two- or three-argument mode, construct PS1 to reference a variable that holds the branch name. Because the shells do not recursively expand, this avoids arbitrary code execution by specially-crafted branch names such as '$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)'. Signed-off-by: Richard Hansen <rhansen@bbn.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Richard Hansen committed
Apr 21, 2014 at 19:53 UTC
8976500cbbb13270398d3b3e07a17b8cc7bff43f
2 files changed
+54
-24
contrib/completion/git-prompt.sh
+32
-2
@@ -207,7 +207,18 @@ __git_ps1_show_upstream ()
207
p=" u+${count#* }-${count% *}" ;;
208
esac
209
if [[ -n "$count" && -n "$name" ]]; then
210
- p="$p $(git rev-parse --abbrev-ref "$upstream" 2>/dev/null)"
210
+ __git_ps1_upstream_name=$(git rev-parse \
211
+ --abbrev-ref "$upstream" 2>/dev/null)
212
+ if [ $pcmode = yes ]; then
213
+ # see the comments around the
214
+ # __git_ps1_branch_name variable below
215
+ p="$p \${__git_ps1_upstream_name}"
216
+ else
217
+ p="$p ${__git_ps1_upstream_name}"
218
+ # not needed anymore; keep user's
219
+ # environment clean
220
+ unset __git_ps1_upstream_name
221
+ fi
222
fi
223
fi
224
@@ -438,8 +449,27 @@ __git_ps1 ()
449
__git_ps1_colorize_gitstring
450
fi
451
452
+ b=${b##refs/heads/}
453
+ if [ $pcmode = yes ]; then
454
+ # In pcmode (and only pcmode) the contents of
455
+ # $gitstring are subject to expansion by the shell.
456
+ # Avoid putting the raw ref name in the prompt to
457
+ # protect the user from arbitrary code execution via
458
+ # specially crafted ref names (e.g., a ref named
459
+ # '$(IFS=_;cmd=sudo_rm_-rf_/;$cmd)' would execute
460
+ # 'sudo rm -rf /' when the prompt is drawn). Instead,
461
+ # put the ref name in a new global variable (in the
462
+ # __git_ps1_* namespace to avoid colliding with the
463
+ # user's environment) and reference that variable from
464
+ # PS1.
465
+ __git_ps1_branch_name=$b
466
+ # note that the $ is escaped -- the variable will be
467
+ # expanded later (when it's time to draw the prompt)
468
+ b="\${__git_ps1_branch_name}"
469
+ fi
470
+
471
local f="$w$i$s$u"
442
- local gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p"
472
+ local gitstring="$c$b${f:+$z$f}$r$p"
473
474
if [ $pcmode = yes ]; then
475
if [ "${__git_printf_supports_v-}" != yes ]; then
t/t9903-bash-prompt.sh
+22
-22
@@ -452,53 +452,53 @@ test_expect_success 'prompt - format string starting with dash' '
452
'
453
454
test_expect_success 'prompt - pc mode' '
455
- printf "BEFORE: (master):AFTER" >expected &&
455
+ printf "BEFORE: (\${__git_ps1_branch_name}):AFTER\\nmaster" >expected &&
456
printf "" >expected_output &&
457
(
458
__git_ps1 "BEFORE:" ":AFTER" >"$actual" &&
459
test_cmp expected_output "$actual" &&
460
- printf "%s" "$PS1" >"$actual"
460
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
461
) &&
462
test_cmp expected "$actual"
463
'
464
465
test_expect_success 'prompt - bash color pc mode - branch name' '
466
- printf "BEFORE: (${c_green}master${c_clear}):AFTER" >expected &&
466
+ printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear}):AFTER\\nmaster" >expected &&
467
(
468
GIT_PS1_SHOWCOLORHINTS=y &&
469
__git_ps1 "BEFORE:" ":AFTER" >"$actual"
470
- printf "%s" "$PS1" >"$actual"
470
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
471
) &&
472
test_cmp expected "$actual"
473
'
474
475
test_expect_success 'prompt - bash color pc mode - detached head' '
476
- printf "BEFORE: (${c_red}(%s...)${c_clear}):AFTER" $(git log -1 --format="%h" b1^) >expected &&
476
+ printf "BEFORE: (${c_red}\${__git_ps1_branch_name}${c_clear}):AFTER\\n(%s...)" $(git log -1 --format="%h" b1^) >expected &&
477
git checkout b1^ &&
478
test_when_finished "git checkout master" &&
479
(
480
GIT_PS1_SHOWCOLORHINTS=y &&
481
__git_ps1 "BEFORE:" ":AFTER" &&
482
- printf "%s" "$PS1" >"$actual"
482
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
483
) &&
484
test_cmp expected "$actual"
485
'
486
487
test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty worktree' '
488
- printf "BEFORE: (${c_green}master${c_clear} ${c_red}*${c_clear}):AFTER" >expected &&
488
+ printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear} ${c_red}*${c_clear}):AFTER\\nmaster" >expected &&
489
echo "dirty" >file &&
490
test_when_finished "git reset --hard" &&
491
(
492
GIT_PS1_SHOWDIRTYSTATE=y &&
493
GIT_PS1_SHOWCOLORHINTS=y &&
494
__git_ps1 "BEFORE:" ":AFTER" &&
495
- printf "%s" "$PS1" >"$actual"
495
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
496
) &&
497
test_cmp expected "$actual"
498
'
499
500
test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty index' '
501
- printf "BEFORE: (${c_green}master${c_clear} ${c_green}+${c_clear}):AFTER" >expected &&
501
+ printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear} ${c_green}+${c_clear}):AFTER\\nmaster" >expected &&
502
echo "dirty" >file &&
503
test_when_finished "git reset --hard" &&
504
git add -u &&
@@ -506,13 +506,13 @@ test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirt
506
GIT_PS1_SHOWDIRTYSTATE=y &&
507
GIT_PS1_SHOWCOLORHINTS=y &&
508
__git_ps1 "BEFORE:" ":AFTER" &&
509
- printf "%s" "$PS1" >"$actual"
509
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
510
) &&
511
test_cmp expected "$actual"
512
'
513
514
test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirty index and worktree' '
515
- printf "BEFORE: (${c_green}master${c_clear} ${c_red}*${c_green}+${c_clear}):AFTER" >expected &&
515
+ printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear} ${c_red}*${c_green}+${c_clear}):AFTER\\nmaster" >expected &&
516
echo "dirty index" >file &&
517
test_when_finished "git reset --hard" &&
518
git add -u &&
@@ -521,25 +521,25 @@ test_expect_success 'prompt - bash color pc mode - dirty status indicator - dirt
521
GIT_PS1_SHOWCOLORHINTS=y &&
522
GIT_PS1_SHOWDIRTYSTATE=y &&
523
__git_ps1 "BEFORE:" ":AFTER" &&
524
- printf "%s" "$PS1" >"$actual"
524
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
525
) &&
526
test_cmp expected "$actual"
527
'
528
529
test_expect_success 'prompt - bash color pc mode - dirty status indicator - before root commit' '
530
- printf "BEFORE: (${c_green}master${c_clear} ${c_green}#${c_clear}):AFTER" >expected &&
530
+ printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear} ${c_green}#${c_clear}):AFTER\\nmaster" >expected &&
531
(
532
GIT_PS1_SHOWDIRTYSTATE=y &&
533
GIT_PS1_SHOWCOLORHINTS=y &&
534
cd otherrepo &&
535
__git_ps1 "BEFORE:" ":AFTER" &&
536
- printf "%s" "$PS1" >"$actual"
536
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
537
) &&
538
test_cmp expected "$actual"
539
'
540
541
test_expect_success 'prompt - bash color pc mode - inside .git directory' '
542
- printf "BEFORE: (${c_green}GIT_DIR!${c_clear}):AFTER" >expected &&
542
+ printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear}):AFTER\\nGIT_DIR!" >expected &&
543
echo "dirty" >file &&
544
test_when_finished "git reset --hard" &&
545
(
@@ -547,13 +547,13 @@ test_expect_success 'prompt - bash color pc mode - inside .git directory' '
547
GIT_PS1_SHOWCOLORHINTS=y &&
548
cd .git &&
549
__git_ps1 "BEFORE:" ":AFTER" &&
550
- printf "%s" "$PS1" >"$actual"
550
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
551
) &&
552
test_cmp expected "$actual"
553
'
554
555
test_expect_success 'prompt - bash color pc mode - stash status indicator' '
556
- printf "BEFORE: (${c_green}master${c_clear} ${c_lblue}\$${c_clear}):AFTER" >expected &&
556
+ printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear} ${c_lblue}\$${c_clear}):AFTER\\nmaster" >expected &&
557
echo 2 >file &&
558
git stash &&
559
test_when_finished "git stash drop" &&
@@ -561,29 +561,29 @@ test_expect_success 'prompt - bash color pc mode - stash status indicator' '
561
GIT_PS1_SHOWSTASHSTATE=y &&
562
GIT_PS1_SHOWCOLORHINTS=y &&
563
__git_ps1 "BEFORE:" ":AFTER" &&
564
- printf "%s" "$PS1" >"$actual"
564
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
565
) &&
566
test_cmp expected "$actual"
567
'
568
569
test_expect_success 'prompt - bash color pc mode - untracked files status indicator' '
570
- printf "BEFORE: (${c_green}master${c_clear} ${c_red}%%${c_clear}):AFTER" >expected &&
570
+ printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear} ${c_red}%%${c_clear}):AFTER\\nmaster" >expected &&
571
(
572
GIT_PS1_SHOWUNTRACKEDFILES=y &&
573
GIT_PS1_SHOWCOLORHINTS=y &&
574
__git_ps1 "BEFORE:" ":AFTER" &&
575
- printf "%s" "$PS1" >"$actual"
575
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
576
) &&
577
test_cmp expected "$actual"
578
'
579
580
test_expect_success 'prompt - zsh color pc mode' '
581
- printf "BEFORE: (%%F{green}master%%f):AFTER" >expected &&
581
+ printf "BEFORE: (%%F{green}\${__git_ps1_branch_name}%%f):AFTER\\nmaster" >expected &&
582
(
583
ZSH_VERSION=5.0.0 &&
584
GIT_PS1_SHOWCOLORHINTS=y &&
585
__git_ps1 "BEFORE:" ":AFTER" >"$actual"
586
- printf "%s" "$PS1" >"$actual"
586
+ printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
587
) &&
588
test_cmp expected "$actual"
589
'