Skip to content

Commit 1c07387

Browse files
committed
[Fix] nvm_download_artifact: reject version strings with disallowed characters
The mirror-supplied (untrusted) version flows into download URLs, filesystem paths, and the checksum awk match. Reject any version outside the node/io.js grammar (`[0-9A-Za-z._+-]`) before it is used. A blocklist of metacharacters is used rather than a strict semver allowlist so RCs, nightlies, v8-canary, and io.js versions still install. Completes the remediation of GHSA-3c52-35h2-gfmm.
1 parent 4967f14 commit 1c07387

2 files changed

Lines changed: 56 additions & 4 deletions

File tree

‎nvm.sh‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2506,10 +2506,16 @@ nvm_download_artifact() {
25062506
local VERSION
25072507
VERSION="${4}"
25082508

2509-
if [ -z "${VERSION}" ]; then
2510-
nvm_err 'A version number is required.'
2511-
return 3
2512-
fi
2509+
case "${VERSION}" in
2510+
'')
2511+
nvm_err 'A version number is required.'
2512+
return 3
2513+
;;
2514+
*[!0-9A-Za-z._+-]*)
2515+
nvm_err 'Invalid version: contains disallowed characters'
2516+
return 3
2517+
;;
2518+
esac
25132519

25142520
if [ "${KIND}" = 'binary' ] && ! nvm_binary_available "${VERSION}"; then
25152521
nvm_err "No precompiled binary available for ${VERSION}."
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/sh
2+
3+
WORK="${TMPDIR:-/tmp}/nvm_version_injection.$$"
4+
PROOF="${WORK}/PWNED"
5+
6+
cleanup () {
7+
unset -f die cleanup nvm_download
8+
rm -rf "${WORK}"
9+
}
10+
die () { echo "$@" ; cleanup ; exit 1; }
11+
12+
\. ../../../nvm.sh
13+
14+
mkdir -p "${WORK}"
15+
export NVM_DIR="${WORK}"
16+
17+
# GHSA-3c52-35h2-gfmm: a mirror-supplied version with shell/awk metacharacters
18+
# must be rejected before it is used in URLs, paths, or awk. Neutralize network.
19+
nvm_download () { return 0; }
20+
21+
# given a version containing command-substitution syntax
22+
# when nvm_download_artifact is asked to download it
23+
# then it is rejected for disallowed characters and nothing is executed
24+
rm -f "${PROOF}"
25+
out="$(nvm_download_artifact node source std 'v1$(touch '"${PROOF}"')' 2>&1 </dev/null)"
26+
case "${out}" in
27+
*'disallowed characters'*) : ;;
28+
*) die "command-substitution version was not rejected; got: ${out}" ;;
29+
esac
30+
[ ! -e "${PROOF}" ] || die 'command-substitution payload executed via nvm_download_artifact'
31+
32+
# and an awk-breakout version is likewise rejected
33+
out="$(nvm_download_artifact node source std 'v1"==$2){system("x")}#' 2>&1 </dev/null)"
34+
case "${out}" in
35+
*'disallowed characters'*) : ;;
36+
*) die "awk-style version was not rejected; got: ${out}" ;;
37+
esac
38+
39+
# and a legitimate nightly version must NOT be rejected by the character guard
40+
out="$(nvm_download_artifact node source std 'v22.0.0-nightly20240101abcdef' 2>&1 </dev/null)"
41+
case "${out}" in
42+
*'disallowed characters'*) die 'a legitimate nightly version was wrongly rejected by the guard' ;;
43+
esac
44+
45+
cleanup
46+
echo 'nvm_download_artifact version injection: passed'

0 commit comments

Comments
 (0)