diff options
author | Chen Qi <Qi.Chen@windriver.com> | 2019-11-06 13:17:48 +0800 |
---|---|---|
committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2019-11-17 22:28:37 -0500 |
commit | 79fb488a7071bbfafb8656adf7826a57b2ef6cd4 (patch) | |
tree | 2eca6b99f527a406d45d20cba0a45f2be6e28d4c | |
parent | 062d9f1f4faf2ca8a1fe78b7c7365e6378c15836 (diff) | |
download | meta-virtualization-79fb488a7071bbfafb8656adf7826a57b2ef6cd4.tar.gz |
runc: fix CVE-2019-16884
Signed-off-by: Chen Qi <Qi.Chen@windriver.com>
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
3 files changed, 203 insertions, 0 deletions
diff --git a/recipes-containers/runc/files/0001-Only-allow-proc-mount-if-it-is-procfs.patch b/recipes-containers/runc/files/0001-Only-allow-proc-mount-if-it-is-procfs.patch new file mode 100644 index 00000000..5aca99e2 --- /dev/null +++ b/recipes-containers/runc/files/0001-Only-allow-proc-mount-if-it-is-procfs.patch | |||
@@ -0,0 +1,201 @@ | |||
1 | From d75b05441772417a0828465a9483f16287937724 Mon Sep 17 00:00:00 2001 | ||
2 | From: Michael Crosby <crosbymichael@gmail.com> | ||
3 | Date: Mon, 23 Sep 2019 16:45:45 -0400 | ||
4 | Subject: [PATCH] Only allow proc mount if it is procfs | ||
5 | |||
6 | Fixes #2128 | ||
7 | |||
8 | This allows proc to be bind mounted for host and rootless namespace usecases but | ||
9 | it removes the ability to mount over the top of proc with a directory. | ||
10 | |||
11 | ```bash | ||
12 | > sudo docker run --rm apparmor | ||
13 | docker: Error response from daemon: OCI runtime create failed: | ||
14 | container_linux.go:346: starting container process caused "process_linux.go:449: | ||
15 | container init caused \"rootfs_linux.go:58: mounting | ||
16 | \\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\" | ||
17 | to rootfs | ||
18 | \\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\" | ||
19 | at \\\"/proc\\\" caused | ||
20 | \\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\" | ||
21 | cannot be mounted because it is not of type proc\\\"\"": unknown. | ||
22 | |||
23 | > sudo docker run --rm -v /proc:/proc apparmor | ||
24 | |||
25 | docker-default (enforce) root 18989 0.9 0.0 1288 4 ? | ||
26 | Ss 16:47 0:00 sleep 20 | ||
27 | ``` | ||
28 | |||
29 | Signed-off-by: Michael Crosby <crosbymichael@gmail.com> | ||
30 | |||
31 | Upstream-Status: Backport [https://github.com/opencontainers/runc/pull/2129/commits/331692baa7afdf6c186f8667cb0e6362ea0802b3] | ||
32 | |||
33 | CVE: CVE-2019-16884 | ||
34 | |||
35 | Signed-off-by: Chen Qi <Qi.Chen@windriver.com> | ||
36 | --- | ||
37 | libcontainer/container_linux.go | 4 +-- | ||
38 | libcontainer/rootfs_linux.go | 50 +++++++++++++++++++++++-------- | ||
39 | libcontainer/rootfs_linux_test.go | 8 ++--- | ||
40 | 3 files changed, 43 insertions(+), 19 deletions(-) | ||
41 | |||
42 | diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go | ||
43 | index 7e58e5e0..d51e35df 100644 | ||
44 | --- a/src/import/libcontainer/container_linux.go | ||
45 | +++ b/src/import/libcontainer/container_linux.go | ||
46 | @@ -19,7 +19,7 @@ import ( | ||
47 | "syscall" // only for SysProcAttr and Signal | ||
48 | "time" | ||
49 | |||
50 | - "github.com/cyphar/filepath-securejoin" | ||
51 | + securejoin "github.com/cyphar/filepath-securejoin" | ||
52 | "github.com/opencontainers/runc/libcontainer/cgroups" | ||
53 | "github.com/opencontainers/runc/libcontainer/configs" | ||
54 | "github.com/opencontainers/runc/libcontainer/intelrdt" | ||
55 | @@ -1160,7 +1160,7 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error { | ||
56 | if err != nil { | ||
57 | return err | ||
58 | } | ||
59 | - if err := checkMountDestination(c.config.Rootfs, dest); err != nil { | ||
60 | + if err := checkProcMount(c.config.Rootfs, dest, ""); err != nil { | ||
61 | return err | ||
62 | } | ||
63 | m.Destination = dest | ||
64 | diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go | ||
65 | index f13b226e..5650b0ac 100644 | ||
66 | --- a/src/import/libcontainer/rootfs_linux.go | ||
67 | +++ b/src/import/libcontainer/rootfs_linux.go | ||
68 | @@ -13,7 +13,7 @@ import ( | ||
69 | "strings" | ||
70 | "time" | ||
71 | |||
72 | - "github.com/cyphar/filepath-securejoin" | ||
73 | + securejoin "github.com/cyphar/filepath-securejoin" | ||
74 | "github.com/mrunalp/fileutils" | ||
75 | "github.com/opencontainers/runc/libcontainer/cgroups" | ||
76 | "github.com/opencontainers/runc/libcontainer/configs" | ||
77 | @@ -197,7 +197,7 @@ func prepareBindMount(m *configs.Mount, rootfs string) error { | ||
78 | if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil { | ||
79 | return err | ||
80 | } | ||
81 | - if err := checkMountDestination(rootfs, dest); err != nil { | ||
82 | + if err := checkProcMount(rootfs, dest, m.Source); err != nil { | ||
83 | return err | ||
84 | } | ||
85 | // update the mount with the correct dest after symlinks are resolved. | ||
86 | @@ -388,7 +388,7 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b | ||
87 | if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil { | ||
88 | return err | ||
89 | } | ||
90 | - if err := checkMountDestination(rootfs, dest); err != nil { | ||
91 | + if err := checkProcMount(rootfs, dest, m.Source); err != nil { | ||
92 | return err | ||
93 | } | ||
94 | // update the mount with the correct dest after symlinks are resolved. | ||
95 | @@ -435,12 +435,12 @@ func getCgroupMounts(m *configs.Mount) ([]*configs.Mount, error) { | ||
96 | return binds, nil | ||
97 | } | ||
98 | |||
99 | -// checkMountDestination checks to ensure that the mount destination is not over the top of /proc. | ||
100 | +// checkProcMount checks to ensure that the mount destination is not over the top of /proc. | ||
101 | // dest is required to be an abs path and have any symlinks resolved before calling this function. | ||
102 | -func checkMountDestination(rootfs, dest string) error { | ||
103 | - invalidDestinations := []string{ | ||
104 | - "/proc", | ||
105 | - } | ||
106 | +// | ||
107 | +// if source is nil, don't stat the filesystem. This is used for restore of a checkpoint. | ||
108 | +func checkProcMount(rootfs, dest, source string) error { | ||
109 | + const procPath = "/proc" | ||
110 | // White list, it should be sub directories of invalid destinations | ||
111 | validDestinations := []string{ | ||
112 | // These entries can be bind mounted by files emulated by fuse, | ||
113 | @@ -463,16 +463,40 @@ func checkMountDestination(rootfs, dest string) error { | ||
114 | return nil | ||
115 | } | ||
116 | } | ||
117 | - for _, invalid := range invalidDestinations { | ||
118 | - path, err := filepath.Rel(filepath.Join(rootfs, invalid), dest) | ||
119 | + path, err := filepath.Rel(filepath.Join(rootfs, procPath), dest) | ||
120 | + if err != nil { | ||
121 | + return err | ||
122 | + } | ||
123 | + // pass if the mount path is located outside of /proc | ||
124 | + if strings.HasPrefix(path, "..") { | ||
125 | + return nil | ||
126 | + } | ||
127 | + if path == "." { | ||
128 | + // an empty source is pasted on restore | ||
129 | + if source == "" { | ||
130 | + return nil | ||
131 | + } | ||
132 | + // only allow a mount on-top of proc if it's source is "proc" | ||
133 | + isproc, err := isProc(source) | ||
134 | if err != nil { | ||
135 | return err | ||
136 | } | ||
137 | - if path != "." && !strings.HasPrefix(path, "..") { | ||
138 | - return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid) | ||
139 | + // pass if the mount is happening on top of /proc and the source of | ||
140 | + // the mount is a proc filesystem | ||
141 | + if isproc { | ||
142 | + return nil | ||
143 | } | ||
144 | + return fmt.Errorf("%q cannot be mounted because it is not of type proc", dest) | ||
145 | } | ||
146 | - return nil | ||
147 | + return fmt.Errorf("%q cannot be mounted because it is inside /proc", dest) | ||
148 | +} | ||
149 | + | ||
150 | +func isProc(path string) (bool, error) { | ||
151 | + var s unix.Statfs_t | ||
152 | + if err := unix.Statfs(path, &s); err != nil { | ||
153 | + return false, err | ||
154 | + } | ||
155 | + return s.Type == unix.PROC_SUPER_MAGIC, nil | ||
156 | } | ||
157 | |||
158 | func setupDevSymlinks(rootfs string) error { | ||
159 | diff --git a/libcontainer/rootfs_linux_test.go b/libcontainer/rootfs_linux_test.go | ||
160 | index d755984b..1bfe7c66 100644 | ||
161 | --- a/src/import/libcontainer/rootfs_linux_test.go | ||
162 | +++ b/src/import/libcontainer/rootfs_linux_test.go | ||
163 | @@ -10,7 +10,7 @@ import ( | ||
164 | |||
165 | func TestCheckMountDestOnProc(t *testing.T) { | ||
166 | dest := "/rootfs/proc/sys" | ||
167 | - err := checkMountDestination("/rootfs", dest) | ||
168 | + err := checkProcMount("/rootfs", dest, "") | ||
169 | if err == nil { | ||
170 | t.Fatal("destination inside proc should return an error") | ||
171 | } | ||
172 | @@ -18,7 +18,7 @@ func TestCheckMountDestOnProc(t *testing.T) { | ||
173 | |||
174 | func TestCheckMountDestOnProcChroot(t *testing.T) { | ||
175 | dest := "/rootfs/proc/" | ||
176 | - err := checkMountDestination("/rootfs", dest) | ||
177 | + err := checkProcMount("/rootfs", dest, "/proc") | ||
178 | if err != nil { | ||
179 | t.Fatal("destination inside proc when using chroot should not return an error") | ||
180 | } | ||
181 | @@ -26,7 +26,7 @@ func TestCheckMountDestOnProcChroot(t *testing.T) { | ||
182 | |||
183 | func TestCheckMountDestInSys(t *testing.T) { | ||
184 | dest := "/rootfs//sys/fs/cgroup" | ||
185 | - err := checkMountDestination("/rootfs", dest) | ||
186 | + err := checkProcMount("/rootfs", dest, "") | ||
187 | if err != nil { | ||
188 | t.Fatal("destination inside /sys should not return an error") | ||
189 | } | ||
190 | @@ -34,7 +34,7 @@ func TestCheckMountDestInSys(t *testing.T) { | ||
191 | |||
192 | func TestCheckMountDestFalsePositive(t *testing.T) { | ||
193 | dest := "/rootfs/sysfiles/fs/cgroup" | ||
194 | - err := checkMountDestination("/rootfs", dest) | ||
195 | + err := checkProcMount("/rootfs", dest, "") | ||
196 | if err != nil { | ||
197 | t.Fatal(err) | ||
198 | } | ||
199 | -- | ||
200 | 2.17.1 | ||
201 | |||
diff --git a/recipes-containers/runc/runc-docker_git.bb b/recipes-containers/runc/runc-docker_git.bb index c9f460b2..8d810d01 100644 --- a/recipes-containers/runc/runc-docker_git.bb +++ b/recipes-containers/runc/runc-docker_git.bb | |||
@@ -7,6 +7,7 @@ SRC_URI = "git://github.com/opencontainers/runc;nobranch=1;name=runc-docker \ | |||
7 | file://0001-runc-Add-console-socket-dev-null.patch \ | 7 | file://0001-runc-Add-console-socket-dev-null.patch \ |
8 | file://0001-Makefile-respect-GOBUILDFLAGS-for-runc-and-remove-re.patch \ | 8 | file://0001-Makefile-respect-GOBUILDFLAGS-for-runc-and-remove-re.patch \ |
9 | file://0001-runc-docker-SIGUSR1-daemonize.patch \ | 9 | file://0001-runc-docker-SIGUSR1-daemonize.patch \ |
10 | file://0001-Only-allow-proc-mount-if-it-is-procfs.patch \ | ||
10 | " | 11 | " |
11 | 12 | ||
12 | RUNC_VERSION = "1.0.0-rc8" | 13 | RUNC_VERSION = "1.0.0-rc8" |
diff --git a/recipes-containers/runc/runc-opencontainers_git.bb b/recipes-containers/runc/runc-opencontainers_git.bb index 361bc94b..3a7e7aaf 100644 --- a/recipes-containers/runc/runc-opencontainers_git.bb +++ b/recipes-containers/runc/runc-opencontainers_git.bb | |||
@@ -4,5 +4,6 @@ SRCREV = "652297c7c7e6c94e8d064ad5916c32891a6fd388" | |||
4 | SRC_URI = " \ | 4 | SRC_URI = " \ |
5 | git://github.com/opencontainers/runc;branch=master \ | 5 | git://github.com/opencontainers/runc;branch=master \ |
6 | file://0001-Makefile-respect-GOBUILDFLAGS-for-runc-and-remove-re.patch \ | 6 | file://0001-Makefile-respect-GOBUILDFLAGS-for-runc-and-remove-re.patch \ |
7 | file://0001-Only-allow-proc-mount-if-it-is-procfs.patch \ | ||
7 | " | 8 | " |
8 | RUNC_VERSION = "1.0.0-rc8" | 9 | RUNC_VERSION = "1.0.0-rc8" |