Skip to content

Commit aba57b0

Browse files
neildmdempsky
authored andcommitted
[release-branch.go1.18] syscall, os/exec: reject environment variables containing NULs
Check for and reject environment variables containing NULs. The conventions for passing environment variables to subprocesses cause most or all systems to interpret a NUL as a separator. The syscall package rejects environment variables containing a NUL on most systems, but erroneously did not do so on Windows. This causes an environment variable such as "FOO=a\x00BAR=b" to be interpreted as "FOO=a", "BAR=b". Check for and reject NULs in environment variables passed to syscall.StartProcess on Windows. Add a redundant check to os/exec as extra insurance. Updates #56284 Fixes #56327 Fixes CVE-2022-41716 Change-Id: I2950e2b0cb14ebd26e5629be1521858f66a7d4ae Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1609434 Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Tatiana Bradley <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> TryBot-Result: Security TryBots <[email protected]> (cherry picked from commit 845accdebb2772c5344ed0c96df9910f3b02d741) Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1617552 Run-TryBot: Tatiana Bradley <[email protected]> Reviewed-by: Damien Neil <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/446915 Reviewed-by: Heschi Kreinick <[email protected]> Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Tatiana Bradley <[email protected]>
1 parent 2c2952a commit aba57b0

File tree

4 files changed

+52
-15
lines changed

4 files changed

+52
-15
lines changed

src/os/exec/env_test.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import (
1111

1212
func TestDedupEnv(t *testing.T) {
1313
tests := []struct {
14-
noCase bool
15-
in []string
16-
want []string
14+
noCase bool
15+
in []string
16+
want []string
17+
wantErr bool
1718
}{
1819
{
1920
noCase: true,
@@ -29,11 +30,17 @@ func TestDedupEnv(t *testing.T) {
2930
in: []string{"=a", "=b", "foo", "bar"},
3031
want: []string{"=b", "foo", "bar"},
3132
},
33+
{
34+
// Filter out entries containing NULs.
35+
in: []string{"A=a\x00b", "B=b", "C\x00C=c"},
36+
want: []string{"B=b"},
37+
wantErr: true,
38+
},
3239
}
3340
for _, tt := range tests {
34-
got := dedupEnvCase(tt.noCase, tt.in)
35-
if !reflect.DeepEqual(got, tt.want) {
36-
t.Errorf("Dedup(%v, %q) = %q; want %q", tt.noCase, tt.in, got, tt.want)
41+
got, err := dedupEnvCase(tt.noCase, tt.in)
42+
if !reflect.DeepEqual(got, tt.want) || (err != nil) != tt.wantErr {
43+
t.Errorf("Dedup(%v, %q) = %q, %v; want %q, error:%v", tt.noCase, tt.in, got, err, tt.want, tt.wantErr)
3744
}
3845
}
3946
}

src/os/exec/exec.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -422,10 +422,14 @@ func (c *Cmd) Start() error {
422422
return err
423423
}
424424

425+
env, err := dedupEnv(envv)
426+
if err != nil {
427+
return err
428+
}
425429
c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
426430
Dir: c.Dir,
427431
Files: c.childFiles,
428-
Env: addCriticalEnv(dedupEnv(envv)),
432+
Env: addCriticalEnv(env),
429433
Sys: c.SysProcAttr,
430434
})
431435
if err != nil {
@@ -741,16 +745,23 @@ func minInt(a, b int) int {
741745
// dedupEnv returns a copy of env with any duplicates removed, in favor of
742746
// later values.
743747
// Items not of the normal environment "key=value" form are preserved unchanged.
744-
func dedupEnv(env []string) []string {
748+
// Items containing NUL characters are removed, and an error is returned along with
749+
// the remaining values.
750+
func dedupEnv(env []string) ([]string, error) {
745751
return dedupEnvCase(runtime.GOOS == "windows", env)
746752
}
747753

748754
// dedupEnvCase is dedupEnv with a case option for testing.
749755
// If caseInsensitive is true, the case of keys is ignored.
750-
func dedupEnvCase(caseInsensitive bool, env []string) []string {
756+
func dedupEnvCase(caseInsensitive bool, env []string) ([]string, error) {
757+
var err error
751758
out := make([]string, 0, len(env))
752759
saw := make(map[string]int, len(env)) // key => index into out
753760
for _, kv := range env {
761+
if strings.IndexByte(kv, 0) != -1 {
762+
err = errors.New("exec: environment variable contains NUL")
763+
continue
764+
}
754765
k, _, ok := strings.Cut(kv, "=")
755766
if !ok {
756767
out = append(out, kv)
@@ -766,7 +777,7 @@ func dedupEnvCase(caseInsensitive bool, env []string) []string {
766777
saw[k] = len(out)
767778
out = append(out, kv)
768779
}
769-
return out
780+
return out, err
770781
}
771782

772783
// addCriticalEnv adds any critical environment variables that are required

src/os/exec/exec_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,15 @@ func TestDedupEnvEcho(t *testing.T) {
10291029
}
10301030
}
10311031

1032+
func TestEnvNULCharacter(t *testing.T) {
1033+
cmd := helperCommand(t, "echoenv", "FOO", "BAR")
1034+
cmd.Env = append(cmd.Env, "FOO=foo\x00BAR=bar")
1035+
out, err := cmd.CombinedOutput()
1036+
if err == nil {
1037+
t.Errorf("output = %q; want error", string(out))
1038+
}
1039+
}
1040+
10321041
func TestString(t *testing.T) {
10331042
echoPath, err := exec.LookPath("echo")
10341043
if err != nil {

src/syscall/exec_windows.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package syscall
88

99
import (
10+
"internal/bytealg"
1011
"runtime"
1112
"sync"
1213
"unicode/utf16"
@@ -115,12 +116,16 @@ func makeCmdLine(args []string) string {
115116
// the representation required by CreateProcess: a sequence of NUL
116117
// terminated strings followed by a nil.
117118
// Last bytes are two UCS-2 NULs, or four NUL bytes.
118-
func createEnvBlock(envv []string) *uint16 {
119+
// If any string contains a NUL, it returns (nil, EINVAL).
120+
func createEnvBlock(envv []string) (*uint16, error) {
119121
if len(envv) == 0 {
120-
return &utf16.Encode([]rune("\x00\x00"))[0]
122+
return &utf16.Encode([]rune("\x00\x00"))[0], nil
121123
}
122124
length := 0
123125
for _, s := range envv {
126+
if bytealg.IndexByteString(s, 0) != -1 {
127+
return nil, EINVAL
128+
}
124129
length += len(s) + 1
125130
}
126131
length += 1
@@ -135,7 +140,7 @@ func createEnvBlock(envv []string) *uint16 {
135140
}
136141
copy(b[i:i+1], []byte{0})
137142

138-
return &utf16.Encode([]rune(string(b)))[0]
143+
return &utf16.Encode([]rune(string(b)))[0], nil
139144
}
140145

141146
func CloseOnExec(fd Handle) {
@@ -400,12 +405,17 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
400405
}
401406
}
402407

408+
envBlock, err := createEnvBlock(attr.Env)
409+
if err != nil {
410+
return 0, 0, err
411+
}
412+
403413
pi := new(ProcessInformation)
404414
flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT
405415
if sys.Token != 0 {
406-
err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, createEnvBlock(attr.Env), dirp, &si.StartupInfo, pi)
416+
err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, envBlock, dirp, &si.StartupInfo, pi)
407417
} else {
408-
err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, createEnvBlock(attr.Env), dirp, &si.StartupInfo, pi)
418+
err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, envBlock, dirp, &si.StartupInfo, pi)
409419
}
410420
if err != nil {
411421
return 0, 0, err

0 commit comments

Comments
 (0)