*: verify that operations on /proc/... are on procfs
This is an additional mitigation for CVE-2019-16884. The primary problem is that Docker can be coerced into bind-mounting a file system on top of /proc (resulting in label-related writes to /proc no longer happening). While we are working on mitigations against permitting the mounts, this helps avoid our code from being tricked into writing to non-procfs files. This is not a perfect solution (after all, there might be a bind-mount of a different procfs file over the target) but in order to exploit that you would need to be able to tweak a config.json pretty specifically (which thankfully Docker doesn't allow). Specifically this stops AppArmor from not labeling a process silently due to /proc/self/attr/... being incorrectly set, and stops any accidental fd leaks because /proc/self/fd/... is not real. Signed-off-by: Aleksa Sarai <asarai@suse.de>
This commit is contained in:
parent
9aef504415
commit
d463f6485b
|
@ -6,6 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsEnabled returns true if apparmor is enabled for the host.
|
// IsEnabled returns true if apparmor is enabled for the host.
|
||||||
|
@ -19,7 +21,7 @@ func IsEnabled() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func setprocattr(attr, value string) error {
|
func setProcAttr(attr, value string) error {
|
||||||
// Under AppArmor you can only change your own attr, so use /proc/self/
|
// Under AppArmor you can only change your own attr, so use /proc/self/
|
||||||
// instead of /proc/<tid>/ like libapparmor does
|
// instead of /proc/<tid>/ like libapparmor does
|
||||||
path := fmt.Sprintf("/proc/self/attr/%s", attr)
|
path := fmt.Sprintf("/proc/self/attr/%s", attr)
|
||||||
|
@ -30,6 +32,10 @@ func setprocattr(attr, value string) error {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := utils.EnsureProcHandle(f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprintf(f, "%s", value)
|
_, err = fmt.Fprintf(f, "%s", value)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -37,7 +43,7 @@ func setprocattr(attr, value string) error {
|
||||||
// changeOnExec reimplements aa_change_onexec from libapparmor in Go
|
// changeOnExec reimplements aa_change_onexec from libapparmor in Go
|
||||||
func changeOnExec(name string) error {
|
func changeOnExec(name string) error {
|
||||||
value := "exec " + name
|
value := "exec " + name
|
||||||
if err := setprocattr("exec", value); err != nil {
|
if err := setProcAttr("exec", value); err != nil {
|
||||||
return fmt.Errorf("apparmor failed to apply profile: %s", err)
|
return fmt.Errorf("apparmor failed to apply profile: %s", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -3,33 +3,57 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// EnsureProcHandle returns whether or not the given file handle is on procfs.
|
||||||
|
func EnsureProcHandle(fh *os.File) error {
|
||||||
|
var buf unix.Statfs_t
|
||||||
|
if err := unix.Fstatfs(int(fh.Fd()), &buf); err != nil {
|
||||||
|
return fmt.Errorf("ensure %s is on procfs: %v", fh.Name(), err)
|
||||||
|
}
|
||||||
|
if buf.Type != unix.PROC_SUPER_MAGIC {
|
||||||
|
return fmt.Errorf("%s is not on procfs", fh.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseExecFrom applies O_CLOEXEC to all file descriptors currently open for
|
||||||
|
// the process (except for those below the given fd value).
|
||||||
func CloseExecFrom(minFd int) error {
|
func CloseExecFrom(minFd int) error {
|
||||||
fdList, err := ioutil.ReadDir("/proc/self/fd")
|
fdDir, err := os.Open("/proc/self/fd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, fi := range fdList {
|
defer fdDir.Close()
|
||||||
fd, err := strconv.Atoi(fi.Name())
|
|
||||||
|
if err := EnsureProcHandle(fdDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fdList, err := fdDir.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, fdStr := range fdList {
|
||||||
|
fd, err := strconv.Atoi(fdStr)
|
||||||
|
// Ignore non-numeric file names.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// ignore non-numeric file names
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Ignore descriptors lower than our specified minimum.
|
||||||
if fd < minFd {
|
if fd < minFd {
|
||||||
// ignore descriptors lower than our specified minimum
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Intentionally ignore errors from unix.CloseOnExec -- the cases where
|
||||||
// intentionally ignore errors from unix.CloseOnExec
|
// this might fail are basically file descriptors that have already
|
||||||
|
// been closed (including and especially the one that was created when
|
||||||
|
// ioutil.ReadDir did the "opendir" syscall).
|
||||||
unix.CloseOnExec(fd)
|
unix.CloseOnExec(fd)
|
||||||
// the cases where this might fail are basically file descriptors that have already been closed (including and especially the one that was created when ioutil.ReadDir did the "opendir" syscall)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue