]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.18] syscall, os/exec: reject environment variables containing...
authorDamien Neil <dneil@google.com>
Tue, 18 Oct 2022 00:38:29 +0000 (17:38 -0700)
committerMatthew Dempsky <mdempsky@google.com>
Tue, 1 Nov 2022 16:15:42 +0000 (16:15 +0000)
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 <dneil@google.com>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
TryBot-Result: Security TryBots <security-trybots@go-security-trybots.iam.gserviceaccount.com>
(cherry picked from commit 845accdebb2772c5344ed0c96df9910f3b02d741)
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1617552
Run-TryBot: Tatiana Bradley <tatianabradley@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/446915
Reviewed-by: Heschi Kreinick <heschi@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Tatiana Bradley <tatiana@golang.org>
src/os/exec/env_test.go
src/os/exec/exec.go
src/os/exec/exec_test.go
src/syscall/exec_windows.go

index b5ac398c274db405b1e2144cb5d6dc3c0cabadfb..47b7c04705729002f5a918866a0967f006eb8ad1 100644 (file)
@@ -11,9 +11,10 @@ import (
 
 func TestDedupEnv(t *testing.T) {
        tests := []struct {
-               noCase bool
-               in     []string
-               want   []string
+               noCase  bool
+               in      []string
+               want    []string
+               wantErr bool
        }{
                {
                        noCase: true,
@@ -29,11 +30,17 @@ func TestDedupEnv(t *testing.T) {
                        in:   []string{"=a", "=b", "foo", "bar"},
                        want: []string{"=b", "foo", "bar"},
                },
+               {
+                       // Filter out entries containing NULs.
+                       in:      []string{"A=a\x00b", "B=b", "C\x00C=c"},
+                       want:    []string{"B=b"},
+                       wantErr: true,
+               },
        }
        for _, tt := range tests {
-               got := dedupEnvCase(tt.noCase, tt.in)
-               if !reflect.DeepEqual(got, tt.want) {
-                       t.Errorf("Dedup(%v, %q) = %q; want %q", tt.noCase, tt.in, got, tt.want)
+               got, err := dedupEnvCase(tt.noCase, tt.in)
+               if !reflect.DeepEqual(got, tt.want) || (err != nil) != tt.wantErr {
+                       t.Errorf("Dedup(%v, %q) = %q, %v; want %q, error:%v", tt.noCase, tt.in, got, err, tt.want, tt.wantErr)
                }
        }
 }
index ecddee690de7c83a84d968bd8113131f274d9400..056a7e07d73ecd8edd9182f4ce372b95f389af46 100644 (file)
@@ -422,10 +422,14 @@ func (c *Cmd) Start() error {
                return err
        }
 
+       env, err := dedupEnv(envv)
+       if err != nil {
+               return err
+       }
        c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
                Dir:   c.Dir,
                Files: c.childFiles,
-               Env:   addCriticalEnv(dedupEnv(envv)),
+               Env:   addCriticalEnv(env),
                Sys:   c.SysProcAttr,
        })
        if err != nil {
@@ -741,16 +745,23 @@ func minInt(a, b int) int {
 // dedupEnv returns a copy of env with any duplicates removed, in favor of
 // later values.
 // Items not of the normal environment "key=value" form are preserved unchanged.
-func dedupEnv(env []string) []string {
+// Items containing NUL characters are removed, and an error is returned along with
+// the remaining values.
+func dedupEnv(env []string) ([]string, error) {
        return dedupEnvCase(runtime.GOOS == "windows", env)
 }
 
 // dedupEnvCase is dedupEnv with a case option for testing.
 // If caseInsensitive is true, the case of keys is ignored.
-func dedupEnvCase(caseInsensitive bool, env []string) []string {
+func dedupEnvCase(caseInsensitive bool, env []string) ([]string, error) {
+       var err error
        out := make([]string, 0, len(env))
        saw := make(map[string]int, len(env)) // key => index into out
        for _, kv := range env {
+               if strings.IndexByte(kv, 0) != -1 {
+                       err = errors.New("exec: environment variable contains NUL")
+                       continue
+               }
                k, _, ok := strings.Cut(kv, "=")
                if !ok {
                        out = append(out, kv)
@@ -766,7 +777,7 @@ func dedupEnvCase(caseInsensitive bool, env []string) []string {
                saw[k] = len(out)
                out = append(out, kv)
        }
-       return out
+       return out, err
 }
 
 // addCriticalEnv adds any critical environment variables that are required
index 1913ae81c8976f1eaa92a2cd81126421847736e7..0be8c6cc18d33b6c8e5c83cdaa83e99e274cf828 100644 (file)
@@ -1029,6 +1029,15 @@ func TestDedupEnvEcho(t *testing.T) {
        }
 }
 
+func TestEnvNULCharacter(t *testing.T) {
+       cmd := helperCommand(t, "echoenv", "FOO", "BAR")
+       cmd.Env = append(cmd.Env, "FOO=foo\x00BAR=bar")
+       out, err := cmd.CombinedOutput()
+       if err == nil {
+               t.Errorf("output = %q; want error", string(out))
+       }
+}
+
 func TestString(t *testing.T) {
        echoPath, err := exec.LookPath("echo")
        if err != nil {
index 9d10d6a51271f62c2b7daa86db0c774dff5084b6..50892bee442f26afdc1fa34a1082bc08c38b0a83 100644 (file)
@@ -7,6 +7,7 @@
 package syscall
 
 import (
+       "internal/bytealg"
        "runtime"
        "sync"
        "unicode/utf16"
@@ -115,12 +116,16 @@ func makeCmdLine(args []string) string {
 // the representation required by CreateProcess: a sequence of NUL
 // terminated strings followed by a nil.
 // Last bytes are two UCS-2 NULs, or four NUL bytes.
-func createEnvBlock(envv []string) *uint16 {
+// If any string contains a NUL, it returns (nil, EINVAL).
+func createEnvBlock(envv []string) (*uint16, error) {
        if len(envv) == 0 {
-               return &utf16.Encode([]rune("\x00\x00"))[0]
+               return &utf16.Encode([]rune("\x00\x00"))[0], nil
        }
        length := 0
        for _, s := range envv {
+               if bytealg.IndexByteString(s, 0) != -1 {
+                       return nil, EINVAL
+               }
                length += len(s) + 1
        }
        length += 1
@@ -135,7 +140,7 @@ func createEnvBlock(envv []string) *uint16 {
        }
        copy(b[i:i+1], []byte{0})
 
-       return &utf16.Encode([]rune(string(b)))[0]
+       return &utf16.Encode([]rune(string(b)))[0], nil
 }
 
 func CloseOnExec(fd Handle) {
@@ -400,12 +405,17 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
                }
        }
 
+       envBlock, err := createEnvBlock(attr.Env)
+       if err != nil {
+               return 0, 0, err
+       }
+
        pi := new(ProcessInformation)
        flags := sys.CreationFlags | CREATE_UNICODE_ENVIRONMENT | _EXTENDED_STARTUPINFO_PRESENT
        if sys.Token != 0 {
-               err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, createEnvBlock(attr.Env), dirp, &si.StartupInfo, pi)
+               err = CreateProcessAsUser(sys.Token, argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, envBlock, dirp, &si.StartupInfo, pi)
        } else {
-               err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, createEnvBlock(attr.Env), dirp, &si.StartupInfo, pi)
+               err = CreateProcess(argv0p, argvp, sys.ProcessAttributes, sys.ThreadAttributes, willInheritHandles, flags, envBlock, dirp, &si.StartupInfo, pi)
        }
        if err != nil {
                return 0, 0, err