Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4af465f

Browse files
committed
Merge pull request moby#5720 from cyphar/5656-cp-absolute-paths
Ensure `docker cp` cannot traverse outside container rootfs
2 parents d066cea + 0fb507d commit 4af465f

4 files changed

Lines changed: 235 additions & 13 deletions

File tree

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Aanand Prasad <[email protected]>
77
Aaron Feng <[email protected]>
88
Abel Muiño <[email protected]>
9+
Aleksa Sarai <[email protected]>
910
Alexander Larsson <[email protected]>
1011
Alexey Shamrin <[email protected]>
1112
Alex Gaynor <[email protected]>

daemon/container.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"log"
1010
"os"
1111
"path"
12+
"path/filepath"
1213
"strings"
1314
"sync"
1415
"syscall"
@@ -89,7 +90,7 @@ func (container *Container) Inject(file io.Reader, pth string) error {
8990
defer container.Unmount()
9091

9192
// Return error if path exists
92-
destPath := path.Join(container.basefs, pth)
93+
destPath := container.getResourcePath(pth)
9394
if _, err := os.Stat(destPath); err == nil {
9495
// Since err is nil, the path could be stat'd and it exists
9596
return fmt.Errorf("%s exists", pth)
@@ -101,7 +102,7 @@ func (container *Container) Inject(file io.Reader, pth string) error {
101102
}
102103

103104
// Make sure the directory exists
104-
if err := os.MkdirAll(path.Join(container.basefs, path.Dir(pth)), 0755); err != nil {
105+
if err := os.MkdirAll(container.getResourcePath(path.Dir(pth)), 0755); err != nil {
105106
return err
106107
}
107108

@@ -170,6 +171,16 @@ func (container *Container) WriteHostConfig() (err error) {
170171
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
171172
}
172173

174+
func (container *Container) getResourcePath(path string) string {
175+
cleanPath := filepath.Join("/", path)
176+
return filepath.Join(container.basefs, cleanPath)
177+
}
178+
179+
func (container *Container) getRootResourcePath(path string) string {
180+
cleanPath := filepath.Join("/", path)
181+
return filepath.Join(container.root, cleanPath)
182+
}
183+
173184
func populateCommand(c *Container, env []string) error {
174185
var (
175186
en *execdriver.Network
@@ -345,7 +356,7 @@ func (container *Container) StderrLogPipe() io.ReadCloser {
345356
}
346357

347358
func (container *Container) buildHostnameFile() error {
348-
container.HostnamePath = path.Join(container.root, "hostname")
359+
container.HostnamePath = container.getRootResourcePath("hostname")
349360
if container.Config.Domainname != "" {
350361
return ioutil.WriteFile(container.HostnamePath, []byte(fmt.Sprintf("%s.%s\n", container.Config.Hostname, container.Config.Domainname)), 0644)
351362
}
@@ -357,7 +368,7 @@ func (container *Container) buildHostnameAndHostsFiles(IP string) error {
357368
return err
358369
}
359370

360-
container.HostsPath = path.Join(container.root, "hosts")
371+
container.HostsPath = container.getRootResourcePath("hosts")
361372

362373
extraContent := make(map[string]string)
363374

@@ -675,19 +686,19 @@ func (container *Container) Unmount() error {
675686
}
676687

677688
func (container *Container) logPath(name string) string {
678-
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name))
689+
return container.getRootResourcePath(fmt.Sprintf("%s-%s.log", container.ID, name))
679690
}
680691

681692
func (container *Container) ReadLog(name string) (io.Reader, error) {
682693
return os.Open(container.logPath(name))
683694
}
684695

685696
func (container *Container) hostConfigPath() string {
686-
return path.Join(container.root, "hostconfig.json")
697+
return container.getRootResourcePath("hostconfig.json")
687698
}
688699

689700
func (container *Container) jsonPath() string {
690-
return path.Join(container.root, "config.json")
701+
return container.getRootResourcePath("config.json")
691702
}
692703

693704
// This method must be exported to be used from the lxc template
@@ -746,8 +757,10 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
746757
if err := container.Mount(); err != nil {
747758
return nil, err
748759
}
760+
749761
var filter []string
750-
basePath := path.Join(container.basefs, resource)
762+
763+
basePath := container.getResourcePath(resource)
751764
stat, err := os.Stat(basePath)
752765
if err != nil {
753766
container.Unmount()
@@ -845,7 +858,7 @@ func (container *Container) setupContainerDns() error {
845858
} else if len(daemon.config.DnsSearch) > 0 {
846859
dnsSearch = daemon.config.DnsSearch
847860
}
848-
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
861+
container.ResolvConfPath = container.getRootResourcePath("resolv.conf")
849862
return resolvconf.Build(container.ResolvConfPath, dns, dnsSearch)
850863
} else {
851864
container.ResolvConfPath = "/etc/resolv.conf"
@@ -983,12 +996,12 @@ func (container *Container) setupWorkingDirectory() error {
983996
if container.Config.WorkingDir != "" {
984997
container.Config.WorkingDir = path.Clean(container.Config.WorkingDir)
985998

986-
pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir))
999+
pthInfo, err := os.Stat(container.getResourcePath(container.Config.WorkingDir))
9871000
if err != nil {
9881001
if !os.IsNotExist(err) {
9891002
return err
9901003
}
991-
if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil {
1004+
if err := os.MkdirAll(container.getResourcePath(container.Config.WorkingDir), 0755); err != nil {
9921005
return err
9931006
}
9941007
}

daemon/volumes.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,11 @@ func applyVolumesFrom(container *Container) error {
9494
if _, exists := container.Volumes[volPath]; exists {
9595
continue
9696
}
97-
stat, err := os.Stat(filepath.Join(c.basefs, volPath))
97+
stat, err := os.Stat(c.getResourcePath(volPath))
9898
if err != nil {
9999
return err
100100
}
101-
if err := createIfNotExists(filepath.Join(container.basefs, volPath), stat.IsDir()); err != nil {
101+
if err := createIfNotExists(container.getResourcePath(volPath), stat.IsDir()); err != nil {
102102
return err
103103
}
104104
container.Volumes[volPath] = id
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
)
10+
11+
const (
12+
cpTestPathParent = "/some"
13+
cpTestPath = "/some/path"
14+
cpTestName = "test"
15+
cpFullPath = "/some/path/test"
16+
17+
cpContainerContents = "holla, i am the container"
18+
cpHostContents = "hello, i am the host"
19+
)
20+
21+
// Test for #5656
22+
// Check that garbage paths don't escape the container's rootfs
23+
func TestCpGarbagePath(t *testing.T) {
24+
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
25+
if err != nil || exitCode != 0 {
26+
t.Fatal("failed to create a container", out, err)
27+
}
28+
29+
cleanedContainerID := stripTrailingCharacters(out)
30+
defer deleteContainer(cleanedContainerID)
31+
32+
out, _, err = cmd(t, "wait", cleanedContainerID)
33+
if err != nil || stripTrailingCharacters(out) != "0" {
34+
t.Fatal("failed to set up container", out, err)
35+
}
36+
37+
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
38+
t.Fatal(err)
39+
}
40+
41+
hostFile, err := os.Create(cpFullPath)
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
defer hostFile.Close()
46+
defer os.RemoveAll(cpTestPathParent)
47+
48+
fmt.Fprintf(hostFile, "%s", cpHostContents)
49+
50+
tmpdir, err := ioutil.TempDir("", "docker-integration")
51+
if err != nil {
52+
t.Fatal(err)
53+
}
54+
55+
tmpname := filepath.Join(tmpdir, cpTestName)
56+
defer os.RemoveAll(tmpdir)
57+
58+
path := filepath.Join("../../../../../../../../../../../../", cpFullPath)
59+
60+
_, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
61+
if err != nil {
62+
t.Fatalf("couldn't copy from garbage path: %s:%s %s", cleanedContainerID, path, err)
63+
}
64+
65+
file, _ := os.Open(tmpname)
66+
defer file.Close()
67+
68+
test, err := ioutil.ReadAll(file)
69+
if err != nil {
70+
t.Fatal(err)
71+
}
72+
73+
if string(test) == cpHostContents {
74+
t.Errorf("output matched host file -- garbage path can escape container rootfs")
75+
}
76+
77+
if string(test) != cpContainerContents {
78+
t.Errorf("output doesn't match the input for garbage path")
79+
}
80+
81+
logDone("cp - garbage paths relative to container's rootfs")
82+
}
83+
84+
// Check that relative paths are relative to the container's rootfs
85+
func TestCpRelativePath(t *testing.T) {
86+
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
87+
if err != nil || exitCode != 0 {
88+
t.Fatal("failed to create a container", out, err)
89+
}
90+
91+
cleanedContainerID := stripTrailingCharacters(out)
92+
defer deleteContainer(cleanedContainerID)
93+
94+
out, _, err = cmd(t, "wait", cleanedContainerID)
95+
if err != nil || stripTrailingCharacters(out) != "0" {
96+
t.Fatal("failed to set up container", out, err)
97+
}
98+
99+
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
100+
t.Fatal(err)
101+
}
102+
103+
hostFile, err := os.Create(cpFullPath)
104+
if err != nil {
105+
t.Fatal(err)
106+
}
107+
defer hostFile.Close()
108+
defer os.RemoveAll(cpTestPathParent)
109+
110+
fmt.Fprintf(hostFile, "%s", cpHostContents)
111+
112+
tmpdir, err := ioutil.TempDir("", "docker-integration")
113+
114+
if err != nil {
115+
t.Fatal(err)
116+
}
117+
118+
tmpname := filepath.Join(tmpdir, cpTestName)
119+
defer os.RemoveAll(tmpdir)
120+
121+
path, _ := filepath.Rel("/", cpFullPath)
122+
123+
_, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
124+
if err != nil {
125+
t.Fatalf("couldn't copy from relative path: %s:%s %s", cleanedContainerID, path, err)
126+
}
127+
128+
file, _ := os.Open(tmpname)
129+
defer file.Close()
130+
131+
test, err := ioutil.ReadAll(file)
132+
if err != nil {
133+
t.Fatal(err)
134+
}
135+
136+
if string(test) == cpHostContents {
137+
t.Errorf("output matched host file -- relative path can escape container rootfs")
138+
}
139+
140+
if string(test) != cpContainerContents {
141+
t.Errorf("output doesn't match the input for relative path")
142+
}
143+
144+
logDone("cp - relative paths relative to container's rootfs")
145+
}
146+
147+
// Check that absolute paths are relative to the container's rootfs
148+
func TestCpAbsolutePath(t *testing.T) {
149+
out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
150+
if err != nil || exitCode != 0 {
151+
t.Fatal("failed to create a container", out, err)
152+
}
153+
154+
cleanedContainerID := stripTrailingCharacters(out)
155+
defer deleteContainer(cleanedContainerID)
156+
157+
out, _, err = cmd(t, "wait", cleanedContainerID)
158+
if err != nil || stripTrailingCharacters(out) != "0" {
159+
t.Fatal("failed to set up container", out, err)
160+
}
161+
162+
if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
163+
t.Fatal(err)
164+
}
165+
166+
hostFile, err := os.Create(cpFullPath)
167+
if err != nil {
168+
t.Fatal(err)
169+
}
170+
defer hostFile.Close()
171+
defer os.RemoveAll(cpTestPathParent)
172+
173+
fmt.Fprintf(hostFile, "%s", cpHostContents)
174+
175+
tmpdir, err := ioutil.TempDir("", "docker-integration")
176+
177+
if err != nil {
178+
t.Fatal(err)
179+
}
180+
181+
tmpname := filepath.Join(tmpdir, cpTestName)
182+
defer os.RemoveAll(tmpdir)
183+
184+
path := cpFullPath
185+
186+
_, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir)
187+
if err != nil {
188+
t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err)
189+
}
190+
191+
file, _ := os.Open(tmpname)
192+
defer file.Close()
193+
194+
test, err := ioutil.ReadAll(file)
195+
if err != nil {
196+
t.Fatal(err)
197+
}
198+
199+
if string(test) == cpHostContents {
200+
t.Errorf("output matched host file -- absolute path can escape container rootfs")
201+
}
202+
203+
if string(test) != cpContainerContents {
204+
t.Errorf("output doesn't match the input for absolute path")
205+
}
206+
207+
logDone("cp - absolute paths relative to container's rootfs")
208+
}

0 commit comments

Comments
 (0)