CVE-2021-3602: Improper Removal of Sensitive Information Before Storage or Transfer (CWE-212)
value: An information disclosure flaw was found in Buildah, when building containers using chroot isolation. Running processes in container builds (e.g. Dockerfile RUN commands) can access environment variables from parent and grandparent processes. When run in a container in a CI/CD environment, environment variables may include sensitive information that was shared with the container in order to be used only by Buildah itself (e.g. container registry credentials).
CWE description: The product stores, transfers, or shares a resource that contains sensitive information, but it does not properly remove that information before the product makes the resource available to unauthorized actors.
CVSS v3 base score: 5.5
Published: 2022-03-03T19:15Z
Repository: https://github.com/containers/buildah
Language: Markdown
Files changed: chroot/run.go, docs/buildah-bud.md, docs/buildah-from.md, docs/buildah-run.md
Commit date: 2021-07-15 10:08:38 -0400
Commit message: chroot: fix environment value leakage to intermediate processes

Blake Burkhart reports that when running processes using "chroot"
isolation, the process being run can examine the environment of its
immediate parent and grandparent processes (CVE-2021-3602).

When run in a container in a CI/CD environment, the environment may
include sensitive information which was shared with the container in
order to be used only by buildah itself.  The command being executed is
able to read such information.

This patch reduces the set of environment variables passed to these
intermediate processes, from all variables to the one which is used to
control the level of debug logging.  It also corrects a misleading debug
message and expands the description of chroot isolation in man pages.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Diff: diff --git a/chroot/run.go b/chroot/run.go
index 7cb1d710..59100356 100644
--- a/chroot/run.go
+++ b/chroot/run.go
@@ -159,11 +159,11 @@ func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reade
 
 	// Start the grandparent subprocess.
 	cmd := unshare.Command(runUsingChrootCommand)
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = stdin, stdout, stderr
 	cmd.Dir = "/"
-	cmd.Env = append([]string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}, os.Environ()...)
+	cmd.Env = []string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}
 
 	logrus.Debugf("Running %#v in %#v", cmd.Cmd, cmd)
 	confwg.Add(1)
 	go func() {
 		_, conferr = io.Copy(pwriter, bytes.NewReader(config))
@@ -205,11 +205,11 @@ func runUsingChrootMain() {
 	if err := json.NewDecoder(confPipe).Decode(&options); err != nil {
 		fmt.Fprintf(os.Stderr, "error decoding options: %v\n", err)
 		os.Exit(1)
 	}
 
-	if options.Spec == nil {
+	if options.Spec == nil || options.Spec.Process == nil {
 		fmt.Fprintf(os.Stderr, "invalid options spec in runUsingChrootMain\n")
 		os.Exit(1)
 	}
 
 	// Prepare to shuttle stdio back and forth.
@@ -571,11 +571,11 @@ func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io
 
 	// Start the parent subprocess.
 	cmd := unshare.Command(append([]string{runUsingChrootExecCommand}, spec.Process.Args...)...)
 	cmd.Stdin, cmd.Stdout, cmd.Stderr = stdin, stdout, stderr
 	cmd.Dir = "/"
-	cmd.Env = append([]string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}, os.Environ()...)
+	cmd.Env = []string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}
 	cmd.UnshareFlags = syscall.CLONE_NEWUTS | syscall.CLONE_NEWNS
 	requestedUserNS := false
 	for _, ns := range spec.Linux.Namespaces {
 		if ns.Type == specs.UserNamespace {
 			requestedUserNS = true
@@ -661,11 +661,11 @@ func runUsingChrootExecMain() {
 	}
 
 	// Set the hostname.  We're already in a distinct UTS namespace and are admins in the user
 	// namespace which created it, so we shouldn't get a permissions error, but seccomp policy
 	// might deny our attempt to call sethostname() anyway, so log a debug message for that.
-	if options.Spec == nil {
+	if options.Spec == nil || options.Spec.Process == nil {
 		fmt.Fprintf(os.Stderr, "invalid options spec passed in\n")
 		os.Exit(1)
 	}
 
 	if options.Spec.Hostname != "" {
@@ -817,11 +817,10 @@ func runUsingChrootExecMain() {
 
 // logNamespaceDiagnostics knows which namespaces we want to create.
 // Output debug messages when that differs from what we're being asked to do.
 func logNamespaceDiagnostics(spec *specs.Spec) {
 	sawMountNS := false
-	sawUserNS := false
 	sawUTSNS := false
 	for _, ns := range spec.Linux.Namespaces {
 		switch ns.Type {
 		case specs.CgroupNamespace:
 			if ns.Path != "" {
@@ -852,26 +851,22 @@ func logNamespaceDiagnostics(spec *specs.Spec) {
 			} else {
 				logrus.Debugf("unable to create PID namespace, sorry about that")
 			}
 		case specs.UserNamespace:
 			if ns.Path != "" {
-				logrus.Debugf("unable to join user namespace %q, creating a new one", ns.Path)
+				logrus.Debugf("unable to join user namespace, sorry about that")
 			}
-			sawUserNS = true
 		case specs.UTSNamespace:
 			if ns.Path != "" {
 				logrus.Debugf("unable to join UTS namespace %q, creating a new one", ns.Path)
 			}
 			sawUTSNS = true
 		}
 	}
 	if !sawMountNS {
 		logrus.Debugf("mount namespace not requested, but creating a new one anyway")
 	}
-	if !sawUserNS {
-		logrus.Debugf("user namespace not requested, but creating a new one anyway")
-	}
 	if !sawUTSNS {
 		logrus.Debugf("UTS namespace not requested, but creating a new one anyway")
 	}
 }
 
diff --git a/docs/buildah-bud.md b/docs/buildah-bud.md
index a2115d86..f3ded098 100644
--- a/docs/buildah-bud.md
+++ b/docs/buildah-bud.md
@@ -305,15 +305,17 @@ another process.
 **--isolation** *type*
 
 Controls what type of isolation is used for running processes as part of `RUN`
 instructions.  Recognized types include *oci* (OCI-compatible runtime, the
 default), *rootless* (OCI-compatible runtime invoked using a modified
-configuration, with *--no-new-keyring* added to its *create*
-invocation, with network and UTS namespaces disabled, and IPC, PID,
-and user namespaces enabled; the default for unprivileged users), and
-*chroot* (an internal wrapper that leans more toward chroot(1) than
-container technology).
+configuration, with *--no-new-keyring* added to its *create* invocation,
+reusing the host's network and UTS namespaces, and creating private IPC, PID,
+mount, and user namespaces; the default for unprivileged users), and *chroot*
+(an internal wrapper that leans more toward chroot(1) than container
+technology, reusing the host's control group, network, IPC, and PID namespaces,
+and creating private mount and UTS namespaces, and creating user namespaces
+only when they're required for ID mapping).
 
 Note: You can also override the default isolation type by setting the
 BUILDAH\_ISOLATION environment variable.  `export BUILDAH_ISOLATION=oci`
 
 **--jobs** *N*
diff --git a/docs/buildah-from.md b/docs/buildah-from.md
index 67e53c5c..281814c6 100644
--- a/docs/buildah-from.md
+++ b/docs/buildah-from.md
@@ -232,15 +232,17 @@ another process.
 **--isolation** *type*
 
 Controls what type of isolation is used for running processes under `buildah
 run`.  Recognized types include *oci* (OCI-compatible runtime, the default),
 *rootless* (OCI-compatible runtime invoked using a modified
-configuration, with *--no-new-keyring* added to its *create*
-invocation, with network and UTS namespaces disabled, and IPC, PID,
-and user namespaces enabled; the default for unprivileged users), and
-*chroot* (an internal wrapper that leans more toward chroot(1) than
-container technology).
+configuration, with *--no-new-keyring* added to its *create* invocation,
+reusing the host's network and UTS namespaces, and creating private IPC, PID,
+mount, and user namespaces; the default for unprivileged users), and *chroot*
+(an internal wrapper that leans more toward chroot(1) than container
+technology, reusing the host's control group, network, IPC, and PID namespaces,
+and creating private mount and UTS namespaces, and creating user namespaces
+only when they're required for ID mapping).
 
 Note: You can also override the default isolation type by setting the
 BUILDAH\_ISOLATION environment variable.  `export BUILDAH_ISOLATION=oci`
 
 **--memory**, **-m**=""
diff --git a/docs/buildah-run.md b/docs/buildah-run.md
index d1922b7e..7bdc9cfe 100644
--- a/docs/buildah-run.md
+++ b/docs/buildah-run.md
@@ -80,14 +80,17 @@ process.
 **--isolation** *type*
 
 Controls what type of isolation is used for running the process.  Recognized
 types include *oci* (OCI-compatible runtime, the default), *rootless*
 (OCI-compatible runtime invoked using a modified configuration, with
-*--no-new-keyring* added to its *create* invocation, with network and
-UTS namespaces disabled, and IPC, PID, and user namespaces enabled;
-the default for unprivileged users), and *chroot* (an internal wrapper
-that leans more toward chroot(1) than container technology).
+*--no-new-keyring* added to its *create* invocation, reusing the host's network
+and UTS namespaces, and creating private IPC, PID, mount, and user namespaces;
+the default for unprivileged users), and *chroot* (an internal wrapper that
+leans more toward chroot(1) than container technology, reusing the host's
+control group, network, IPC, and PID namespaces, and creating private mount and
+UTS namespaces, and creating user namespaces only when they're required for ID
+mapping).
 
 Note: You can also override the default isolation type by setting the
 BUILDAH\_ISOLATION environment variable.  `export BUILDAH_ISOLATION=oci`
 
 **--mount**=*type=TYPE,TYPE-SPECIFIC-OPTION[,...]*

Vulnerable code: 	cmd.Env = append([]string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}, os.Environ()...)
	if options.Spec == nil {
	cmd.Env = append([]string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}, os.Environ()...)
	if options.Spec == nil {
	sawUserNS := false
				logrus.Debugf("unable to join user namespace %q, creating a new one", ns.Path)
			sawUserNS = true
	if !sawUserNS {
		logrus.Debugf("user namespace not requested, but creating a new one anyway")
	}
configuration, with *--no-new-keyring* added to its *create*
invocation, with network and UTS namespaces disabled, and IPC, PID,
and user namespaces enabled; the default for unprivileged users), and
*chroot* (an internal wrapper that leans more toward chroot(1) than
container technology).
configuration, with *--no-new-keyring* added to its *create*
invocation, with network and UTS namespaces disabled, and IPC, PID,
and user namespaces enabled; the default for unprivileged users), and
*chroot* (an internal wrapper that leans more toward chroot(1) than
container technology).
*--no-new-keyring* added to its *create* invocation, with network and
UTS namespaces disabled, and IPC, PID, and user namespaces enabled;
the default for unprivileged users), and *chroot* (an internal wrapper
that leans more toward chroot(1) than container technology).
Fixed code: 	cmd.Env = []string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}
	if options.Spec == nil || options.Spec.Process == nil {
	cmd.Env = []string{fmt.Sprintf("LOGLEVEL=%d", logrus.GetLevel())}
	if options.Spec == nil || options.Spec.Process == nil {
				logrus.Debugf("unable to join user namespace, sorry about that")
configuration, with *--no-new-keyring* added to its *create* invocation,
reusing the host's network and UTS namespaces, and creating private IPC, PID,
mount, and user namespaces; the default for unprivileged users), and *chroot*
(an internal wrapper that leans more toward chroot(1) than container
technology, reusing the host's control group, network, IPC, and PID namespaces,
and creating private mount and UTS namespaces, and creating user namespaces
only when they're required for ID mapping).
configuration, with *--no-new-keyring* added to its *create* invocation,
reusing the host's network and UTS namespaces, and creating private IPC, PID,
mount, and user namespaces; the default for unprivileged users), and *chroot*
(an internal wrapper that leans more toward chroot(1) than container
technology, reusing the host's control group, network, IPC, and PID namespaces,
and creating private mount and UTS namespaces, and creating user namespaces
only when they're required for ID mapping).
*--no-new-keyring* added to its *create* invocation, reusing the host's network
and UTS namespaces, and creating private IPC, PID, mount, and user namespaces;
the default for unprivileged users), and *chroot* (an internal wrapper that
leans more toward chroot(1) than container technology, reusing the host's
control group, network, IPC, and PID namespaces, and creating private mount and
UTS namespaces, and creating user namespaces only when they're required for ID
mapping).