From 90bb88748ba6c29c2cec73b18ed7057413aef308 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 3 Jun 2026 13:11:26 -0700 Subject: [PATCH] [Fix] `nvm_get_checksum`: pass the tarball name to awk as data, not program text The awk program string-interpolated the slug (which embeds the untrusted, mirror-supplied version) into its source, so a crafted version such as `v1"==$2){system("touch${IFS}/tmp/x")}#` was executed by awk's `system()`. Pass the value via `-v tarball=...` so awk treats it as data and never as code. See GHSA-3c52-35h2-gfmm (a second injection sink fed by the same untrusted version field that `nvm_download`'s eval was; the source-install path reaches this during a normal `nvm install `). --- nvm.sh | 2 +- .../Unit tests/nvm_get_checksum awk injection | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100755 test/fast/Unit tests/nvm_get_checksum awk injection diff --git a/nvm.sh b/nvm.sh index e59cf3f2..486126c6 100755 --- a/nvm.sh +++ b/nvm.sh @@ -1927,7 +1927,7 @@ nvm_get_checksum() { SHASUMS_URL="${MIRROR}/${3}/SHASUMS.txt" fi - nvm_download -L -s "${SHASUMS_URL}" -o - | command awk "{ if (\"${4}.${5}\" == \$2) print \$1}" + nvm_download -L -s "${SHASUMS_URL}" -o - | command awk -v tarball="${4}.${5}" '{ if (tarball == $2) print $1 }' } nvm_print_versions() { diff --git a/test/fast/Unit tests/nvm_get_checksum awk injection b/test/fast/Unit tests/nvm_get_checksum awk injection new file mode 100755 index 00000000..63914e30 --- /dev/null +++ b/test/fast/Unit tests/nvm_get_checksum awk injection @@ -0,0 +1,28 @@ +#!/bin/sh + +WORK="${TMPDIR:-/tmp}/nvm_get_checksum_awk.$$" +PROOF="${WORK}/PWNED" + +cleanup () { + unset -f die cleanup nvm_download + rm -rf "${WORK}" +} +die () { echo "$@" ; cleanup ; exit 1; } + +\. ../../../nvm.sh + +mkdir -p "${WORK}" + +# GHSA-3c52-35h2-gfmm: nvm_get_checksum must treat the (untrusted, version-derived) +# slug as awk data, never as awk program text. +# given a crafted slug carrying an unconditional awk system() action +# and a mock that supplies one SHASUMS record (so such an action would fire) +nvm_download () { printf 'deadbeef sometarball\n'; } +# when nvm_get_checksum runs with that slug as its 4th argument +rm -f "${PROOF}" +nvm_get_checksum node std v1 'x" == $2) print $1} {system("touch${IFS}'"$PROOF"'")} #' tar.gz >/dev/null 2>&1 +# then the injected awk code must not execute +[ ! -e "${PROOF}" ] || die 'awk injection fires in nvm_get_checksum (slug interpolated into awk program text)' + +cleanup +echo 'nvm_get_checksum awk injection: passed'