ssh/tailssh: reject dangerous LD_/DYLD_ env vars in acceptEnv filtering (#19914)

Block dynamic linker environment variables (LD_PRELOAD, LD_LIBRARY_PATH,
DYLD_INSERT_LIBRARIES, and friends) from being forwarded regardless of
acceptEnv policy, preventing privilege escalation via wildcard patterns
like "*".

We are not aware of any legitimate use of these variables so they are
safe to exclude from being passed.

Thanks to Tim Sageser (dtrsecurity) for this report.

Updates tailscale/corp#42033

Signed-off-by: Patrick O'Doherty <patrick@tailscale.com>
This commit is contained in:
Patrick O'Doherty
2026-06-01 09:19:27 -07:00
committed by GitHub
parent 2ba426802f
commit 651049ec19
2 changed files with 90 additions and 0 deletions
+69
View File
@@ -10,6 +10,39 @@ import (
"github.com/google/go-cmp/cmp"
)
func TestIsDangerousEnvVar(t *testing.T) {
tests := []struct {
name string
dangerous bool
}{
{"LD_PRELOAD", true},
{"LD_LIBRARY_PATH", true},
{"LD_AUDIT", true},
{"LD_DEBUG", true},
{"LD_PROFILE", true},
{"ld_preload", true},
{"DYLD_INSERT_LIBRARIES", true},
{"DYLD_LIBRARY_PATH", true},
{"DYLD_FRAMEWORK_PATH", true},
{"dyld_insert_libraries", true},
{"TERM", false},
{"LANG", false},
{"LC_ALL", false},
{"PATH", false},
{"HOME", false},
{"LDFLAGS", false},
{"MY_LD_PRELOAD", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isDangerousEnvVar(tt.name); got != tt.dangerous {
t.Errorf("isDangerousEnvVar(%q) = %v, want %v", tt.name, got, tt.dangerous)
}
})
}
}
func TestMatchAcceptEnvPattern(t *testing.T) {
testCases := []struct {
pattern string
@@ -135,6 +168,42 @@ func TestFilterEnv(t *testing.T) {
expectedFiltered: nil,
wantErrMessage: `invalid environment variable: "FOOBAR". Variables must be in "KEY=VALUE" format`,
},
{
name: "ld-preload-rejected-with-wildcard",
acceptEnv: []string{"*"},
environ: []string{"LD_PRELOAD=/tmp/evil.so", "TERM=xterm"},
expectedFiltered: []string{"TERM=xterm"},
},
{
name: "ld-vars-rejected-with-wildcard",
acceptEnv: []string{"*"},
environ: []string{"LD_PRELOAD=/tmp/evil.so", "LD_LIBRARY_PATH=/tmp", "LD_AUDIT=/tmp/audit.so", "SAFE_VAR=ok"},
expectedFiltered: []string{"SAFE_VAR=ok"},
},
{
name: "ld-vars-rejected-with-explicit-match",
acceptEnv: []string{"LD_PRELOAD", "LD_LIBRARY_PATH"},
environ: []string{"LD_PRELOAD=/tmp/evil.so", "LD_LIBRARY_PATH=/tmp"},
expectedFiltered: nil,
},
{
name: "ld-vars-rejected-with-prefix-pattern",
acceptEnv: []string{"LD_*"},
environ: []string{"LD_PRELOAD=/tmp/evil.so", "LD_LIBRARY_PATH=/tmp"},
expectedFiltered: nil,
},
{
name: "ld-vars-case-insensitive",
acceptEnv: []string{"*"},
environ: []string{"ld_preload=/tmp/evil.so", "Ld_Library_Path=/tmp", "SAFE=ok"},
expectedFiltered: []string{"SAFE=ok"},
},
{
name: "dyld-vars-rejected",
acceptEnv: []string{"*"},
environ: []string{"DYLD_INSERT_LIBRARIES=/tmp/evil.dylib", "DYLD_LIBRARY_PATH=/tmp", "TERM=xterm"},
expectedFiltered: []string{"TERM=xterm"},
},
}
for _, tc := range testCases {