Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
8db8423e
Commit
8db8423e
authored
Apr 16, 2019
by
Maciej Szulik
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Properly handle links in tar
parent
fe44df8b
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
148 additions
and
144 deletions
+148
-144
BUILD
pkg/kubectl/cmd/cp/BUILD
+1
-0
cp.go
pkg/kubectl/cmd/cp/cp.go
+56
-44
cp_test.go
pkg/kubectl/cmd/cp/cp_test.go
+91
-100
No files found.
pkg/kubectl/cmd/cp/BUILD
View file @
8db8423e
...
@@ -32,6 +32,7 @@ go_test(
...
@@ -32,6 +32,7 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library",
],
],
)
)
...
...
pkg/kubectl/cmd/cp/cp.go
View file @
8db8423e
...
@@ -418,9 +418,7 @@ func clean(fileName string) string {
...
@@ -418,9 +418,7 @@ func clean(fileName string) string {
return
path
.
Clean
(
string
(
os
.
PathSeparator
)
+
fileName
)
return
path
.
Clean
(
string
(
os
.
PathSeparator
)
+
fileName
)
}
}
func
(
o
*
CopyOptions
)
untarAll
(
reader
io
.
Reader
,
destFile
,
prefix
string
)
error
{
func
(
o
*
CopyOptions
)
untarAll
(
reader
io
.
Reader
,
destDir
,
prefix
string
)
error
{
entrySeq
:=
-
1
// TODO: use compression here?
// TODO: use compression here?
tarReader
:=
tar
.
NewReader
(
reader
)
tarReader
:=
tar
.
NewReader
(
reader
)
for
{
for
{
...
@@ -431,52 +429,60 @@ func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error
...
@@ -431,52 +429,60 @@ func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error
}
}
break
break
}
}
entrySeq
++
mode
:=
header
.
FileInfo
()
.
Mode
()
// All the files will start with the prefix, which is the directory where
// all the files will start with the prefix, which is the directory where
// they were located on the pod, we need to strip down that prefix, but
// they were located on the pod, we need to strip down that prefix, but
// if the prefix is missing it means the tar was tempered with
// if the prefix is missing it means the tar was tempered with.
// For the case where prefix is empty we need to ensure that the path
// is not absolute, which also indicates the tar file was tempered with.
if
!
strings
.
HasPrefix
(
header
.
Name
,
prefix
)
{
if
!
strings
.
HasPrefix
(
header
.
Name
,
prefix
)
{
return
fmt
.
Errorf
(
"tar contents corrupted"
)
return
fmt
.
Errorf
(
"tar contents corrupted"
)
}
}
outFileName
:=
path
.
Join
(
destFile
,
clean
(
header
.
Name
[
len
(
prefix
)
:
]))
baseName
:=
path
.
Dir
(
outFileName
)
// basic file information
mode
:=
header
.
FileInfo
()
.
Mode
()
destFileName
:=
path
.
Join
(
destDir
,
header
.
Name
[
len
(
prefix
)
:
])
baseName
:=
path
.
Dir
(
destFileName
)
if
err
:=
os
.
MkdirAll
(
baseName
,
0755
);
err
!=
nil
{
if
err
:=
os
.
MkdirAll
(
baseName
,
0755
);
err
!=
nil
{
return
err
return
err
}
}
if
header
.
FileInfo
()
.
IsDir
()
{
if
header
.
FileInfo
()
.
IsDir
()
{
if
err
:=
os
.
MkdirAll
(
ou
tFileName
,
0755
);
err
!=
nil
{
if
err
:=
os
.
MkdirAll
(
des
tFileName
,
0755
);
err
!=
nil
{
return
err
return
err
}
}
continue
continue
}
}
// handle coping remote file into local directory
// We need to ensure that the destination file is always within boundries
if
entrySeq
==
0
&&
!
header
.
FileInfo
()
.
IsDir
()
{
// of the destination directory. This prevents any kind of path traversal
exists
,
err
:=
dirExists
(
outFileName
)
// from within tar archive.
if
err
!=
nil
{
dir
,
file
:=
filepath
.
Split
(
destFileName
)
return
err
evaledPath
,
err
:=
filepath
.
EvalSymlinks
(
dir
)
}
if
err
!=
nil
{
if
exists
{
return
err
outFileName
=
filepath
.
Join
(
outFileName
,
path
.
Base
(
clean
(
header
.
Name
)))
}
}
// For scrutiny we verify both the actual destination as well as we follow
// all the links that might lead outside of the destination directory.
if
!
isDestRelative
(
destDir
,
destFileName
)
||
!
isDestRelative
(
destDir
,
filepath
.
Join
(
evaledPath
,
file
))
{
fmt
.
Fprintf
(
o
.
IOStreams
.
ErrOut
,
"warning: link %q is pointing to %q which is outside target destination, skipping
\n
"
,
destFileName
,
header
.
Linkname
)
continue
}
}
if
mode
&
os
.
ModeSymlink
!=
0
{
if
mode
&
os
.
ModeSymlink
!=
0
{
linkname
:=
header
.
Linkname
linkname
:=
header
.
Linkname
// error is returned if linkname can't be made relative to destFile,
// We need to ensure that the link destination is always within boundries
// but relative can end up being ../dir that's why we also need to
// of the destination directory. This prevents any kind of path traversal
// verify if relative path is the same after Clean-ing
// from within tar archive.
relative
,
err
:=
filepath
.
Rel
(
destFile
,
linkname
)
if
!
isDestRelative
(
destDir
,
linkJoin
(
destFileName
,
linkname
))
{
if
path
.
IsAbs
(
linkname
)
&&
(
err
!=
nil
||
relative
!=
stripPathShortcuts
(
relative
))
{
fmt
.
Fprintf
(
o
.
IOStreams
.
ErrOut
,
"warning: link %q is pointing to %q which is outside target destination, skipping
\n
"
,
destFileName
,
header
.
Linkname
)
fmt
.
Fprintf
(
o
.
IOStreams
.
ErrOut
,
"warning: link %q is pointing to %q which is outside target destination, skipping
\n
"
,
outFileName
,
header
.
Linkname
)
continue
continue
}
}
if
err
:=
os
.
Symlink
(
linkname
,
ou
tFileName
);
err
!=
nil
{
if
err
:=
os
.
Symlink
(
linkname
,
des
tFileName
);
err
!=
nil
{
return
err
return
err
}
}
}
else
{
}
else
{
outFile
,
err
:=
os
.
Create
(
ou
tFileName
)
outFile
,
err
:=
os
.
Create
(
des
tFileName
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
...
@@ -490,14 +496,32 @@ func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error
...
@@ -490,14 +496,32 @@ func (o *CopyOptions) untarAll(reader io.Reader, destFile, prefix string) error
}
}
}
}
if
entrySeq
==
-
1
{
//if no file was copied
errInfo
:=
fmt
.
Sprintf
(
"error: %s no such file or directory"
,
prefix
)
return
errors
.
New
(
errInfo
)
}
return
nil
return
nil
}
}
// linkJoin joins base and link to get the final path to be created.
// It will consider whether link is an absolute path or not when returning result.
func
linkJoin
(
base
,
link
string
)
string
{
if
filepath
.
IsAbs
(
link
)
{
return
link
}
return
filepath
.
Join
(
base
,
link
)
}
// isDestRelative returns true if dest is pointing outside the base directory,
// false otherwise.
func
isDestRelative
(
base
,
dest
string
)
bool
{
fullPath
:=
dest
if
!
filepath
.
IsAbs
(
dest
)
{
fullPath
=
filepath
.
Join
(
base
,
dest
)
}
relative
,
err
:=
filepath
.
Rel
(
base
,
fullPath
)
if
err
!=
nil
{
return
false
}
return
relative
==
"."
||
relative
==
stripPathShortcuts
(
relative
)
}
func
getPrefix
(
file
string
)
string
{
func
getPrefix
(
file
string
)
string
{
// tar strips the leading '/' if it's there, so we will too
// tar strips the leading '/' if it's there, so we will too
return
strings
.
TrimLeft
(
file
,
"/"
)
return
strings
.
TrimLeft
(
file
,
"/"
)
...
@@ -524,15 +548,3 @@ func (o *CopyOptions) execute(options *exec.ExecOptions) error {
...
@@ -524,15 +548,3 @@ func (o *CopyOptions) execute(options *exec.ExecOptions) error {
}
}
return
nil
return
nil
}
}
// dirExists checks if a path exists and is a directory.
func
dirExists
(
path
string
)
(
bool
,
error
)
{
fi
,
err
:=
os
.
Stat
(
path
)
if
err
==
nil
&&
fi
.
IsDir
()
{
return
true
,
nil
}
if
os
.
IsNotExist
(
err
)
{
return
false
,
nil
}
return
false
,
err
}
pkg/kubectl/cmd/cp/cp_test.go
View file @
8db8423e
...
@@ -19,11 +19,11 @@ package cp
...
@@ -19,11 +19,11 @@ package cp
import
(
import
(
"archive/tar"
"archive/tar"
"bytes"
"bytes"
"fmt"
"io"
"io"
"io/ioutil"
"io/ioutil"
"net/http"
"net/http"
"os"
"os"
"os/exec"
"path"
"path"
"path/filepath"
"path/filepath"
"reflect"
"reflect"
...
@@ -31,6 +31,7 @@ import (
...
@@ -31,6 +31,7 @@ import (
"testing"
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
"k8s.io/api/core/v1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime"
...
@@ -191,6 +192,67 @@ func TestStripPathShortcuts(t *testing.T) {
...
@@ -191,6 +192,67 @@ func TestStripPathShortcuts(t *testing.T) {
}
}
}
}
}
}
func
TestIsDestRelative
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
base
string
dest
string
relative
bool
}{
{
base
:
"/dir"
,
dest
:
"../link"
,
relative
:
false
,
},
{
base
:
"/dir"
,
dest
:
"../../link"
,
relative
:
false
,
},
{
base
:
"/dir"
,
dest
:
"/link"
,
relative
:
false
,
},
{
base
:
"/dir"
,
dest
:
"link"
,
relative
:
true
,
},
{
base
:
"/dir"
,
dest
:
"int/file/link"
,
relative
:
true
,
},
{
base
:
"/dir"
,
dest
:
"int/../link"
,
relative
:
true
,
},
{
base
:
"/dir"
,
dest
:
"/dir/link"
,
relative
:
true
,
},
{
base
:
"/dir"
,
dest
:
"/dir/int/../link"
,
relative
:
true
,
},
{
base
:
"/dir"
,
dest
:
"/dir/../../link"
,
relative
:
false
,
},
}
for
i
,
test
:=
range
tests
{
t
.
Run
(
fmt
.
Sprintf
(
"%d"
,
i
),
func
(
t
*
testing
.
T
)
{
if
test
.
relative
!=
isDestRelative
(
test
.
base
,
test
.
dest
)
{
t
.
Errorf
(
"unexpected result for: base %q, dest %q"
,
test
.
base
,
test
.
dest
)
}
})
}
}
func
checkErr
(
t
*
testing
.
T
,
err
error
)
{
func
checkErr
(
t
*
testing
.
T
,
err
error
)
{
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -367,97 +429,6 @@ func TestTarUntarWrongPrefix(t *testing.T) {
...
@@ -367,97 +429,6 @@ func TestTarUntarWrongPrefix(t *testing.T) {
}
}
}
}
// TestCopyToLocalFileOrDir tests untarAll in two cases :
// 1: copy pod file to local file
// 2: copy pod file into local directory
func
TestCopyToLocalFileOrDir
(
t
*
testing
.
T
)
{
dir
,
err
:=
ioutil
.
TempDir
(
os
.
TempDir
(),
"input"
)
dir2
,
err2
:=
ioutil
.
TempDir
(
os
.
TempDir
(),
"output"
)
if
err
!=
nil
||
err2
!=
nil
{
t
.
Errorf
(
"unexpected error: %v | %v"
,
err
,
err2
)
t
.
FailNow
()
}
defer
func
()
{
if
err
:=
os
.
RemoveAll
(
dir
);
err
!=
nil
{
t
.
Errorf
(
"Unexpected error cleaning up: %v"
,
err
)
}
if
err
:=
os
.
RemoveAll
(
dir2
);
err
!=
nil
{
t
.
Errorf
(
"Unexpected error cleaning up: %v"
,
err
)
}
}()
files
:=
[]
struct
{
name
string
data
string
dest
string
destDirExists
bool
}{
{
name
:
"foo"
,
data
:
"foobarbaz"
,
dest
:
"path/to/dest"
,
destDirExists
:
false
,
},
{
name
:
"dir/blah"
,
data
:
"bazblahfoo"
,
dest
:
"dest/file/path"
,
destDirExists
:
true
,
},
}
for
_
,
file
:=
range
files
{
func
()
{
// setup
srcFilePath
:=
filepath
.
Join
(
dir
,
file
.
name
)
destPath
:=
filepath
.
Join
(
dir2
,
file
.
dest
)
if
err
:=
os
.
MkdirAll
(
filepath
.
Dir
(
srcFilePath
),
0755
);
err
!=
nil
{
t
.
Errorf
(
"unexpected error: %v"
,
err
)
t
.
FailNow
()
}
createTmpFile
(
t
,
srcFilePath
,
file
.
data
)
if
file
.
destDirExists
{
if
err
:=
os
.
MkdirAll
(
destPath
,
0755
);
err
!=
nil
{
t
.
Errorf
(
"unexpected error: %v"
,
err
)
t
.
FailNow
()
}
}
// start tests
srcTarFilePath
:=
filepath
.
Join
(
dir
,
file
.
name
+
".tar"
)
// here use tar command to create tar file instead of calling makeTar func
// because makeTar func can not generate correct header name
err
=
exec
.
Command
(
"tar"
,
"cf"
,
srcTarFilePath
,
srcFilePath
)
.
Run
()
if
err
!=
nil
{
t
.
Errorf
(
"unexpected error: %v"
,
err
)
t
.
FailNow
()
}
srcTarFile
,
err
:=
os
.
Open
(
srcTarFilePath
)
if
err
!=
nil
{
t
.
Errorf
(
"unexpected error: %v"
,
err
)
t
.
FailNow
()
}
defer
srcTarFile
.
Close
()
opts
:=
NewCopyOptions
(
genericclioptions
.
NewTestIOStreamsDiscard
())
if
err
:=
opts
.
untarAll
(
srcTarFile
,
destPath
,
getPrefix
(
srcFilePath
));
err
!=
nil
{
t
.
Errorf
(
"unexpected error: %v"
,
err
)
t
.
FailNow
()
}
actualDestFilePath
:=
destPath
if
file
.
destDirExists
{
actualDestFilePath
=
filepath
.
Join
(
destPath
,
filepath
.
Base
(
srcFilePath
))
}
_
,
err
=
os
.
Stat
(
actualDestFilePath
)
if
err
!=
nil
&&
os
.
IsNotExist
(
err
)
{
t
.
Errorf
(
"expecting %s exists, but actually it's missing"
,
actualDestFilePath
)
}
cmpFileData
(
t
,
actualDestFilePath
,
file
.
data
)
}()
}
}
func
TestTarDestinationName
(
t
*
testing
.
T
)
{
func
TestTarDestinationName
(
t
*
testing
.
T
)
{
dir
,
err
:=
ioutil
.
TempDir
(
os
.
TempDir
(),
"input"
)
dir
,
err
:=
ioutil
.
TempDir
(
os
.
TempDir
(),
"input"
)
dir2
,
err2
:=
ioutil
.
TempDir
(
os
.
TempDir
(),
"output"
)
dir2
,
err2
:=
ioutil
.
TempDir
(
os
.
TempDir
(),
"output"
)
...
@@ -544,7 +515,6 @@ func TestBadTar(t *testing.T) {
...
@@ -544,7 +515,6 @@ func TestBadTar(t *testing.T) {
name
string
name
string
body
string
body
string
}{
}{
{
"/prefix/../../../tmp/foo"
,
"Up to temp"
},
{
"/prefix/foo/bar/../../home/bburns/names.txt"
,
"Down and back"
},
{
"/prefix/foo/bar/../../home/bburns/names.txt"
,
"Down and back"
},
}
}
for
_
,
file
:=
range
files
{
for
_
,
file
:=
range
files
{
...
@@ -801,13 +771,13 @@ func TestUntar(t *testing.T) {
...
@@ -801,13 +771,13 @@ func TestUntar(t *testing.T) {
expected
:
filepath
.
Join
(
testdir
,
dest
,
"relative"
),
expected
:
filepath
.
Join
(
testdir
,
dest
,
"relative"
),
},
{
// Relative file outside dest
},
{
// Relative file outside dest
path
:
"../unrelative"
,
path
:
"../unrelative"
,
expected
:
filepath
.
Join
(
testdir
,
dest
,
"unrelative"
)
,
expected
:
""
,
},
{
// Nested relative file inside dest
},
{
// Nested relative file inside dest
path
:
"nested/nest-rel"
,
path
:
"nested/nest-rel"
,
expected
:
filepath
.
Join
(
testdir
,
dest
,
"nested/nest-rel"
),
expected
:
filepath
.
Join
(
testdir
,
dest
,
"nested/nest-rel"
),
},
{
// Nested relative file outside dest
},
{
// Nested relative file outside dest
path
:
"nested/../../nest-unrelative"
,
path
:
"nested/../../nest-unrelative"
,
expected
:
filepath
.
Join
(
testdir
,
dest
,
"nest-unrelative"
)
,
expected
:
""
,
}}
}}
mkExpectation
:=
func
(
expected
,
suffix
string
)
string
{
mkExpectation
:=
func
(
expected
,
suffix
string
)
string
{
...
@@ -816,6 +786,13 @@ func TestUntar(t *testing.T) {
...
@@ -816,6 +786,13 @@ func TestUntar(t *testing.T) {
}
}
return
expected
+
suffix
return
expected
+
suffix
}
}
mkBacktickExpectation
:=
func
(
expected
,
suffix
string
)
string
{
dir
,
_
:=
filepath
.
Split
(
filepath
.
Clean
(
expected
))
if
len
(
strings
.
Split
(
dir
,
string
(
os
.
PathSeparator
)))
<=
1
{
return
""
}
return
expected
+
suffix
}
links
:=
[]
file
{}
links
:=
[]
file
{}
for
_
,
f
:=
range
files
{
for
_
,
f
:=
range
files
{
links
=
append
(
links
,
file
{
links
=
append
(
links
,
file
{
...
@@ -825,11 +802,11 @@ func TestUntar(t *testing.T) {
...
@@ -825,11 +802,11 @@ func TestUntar(t *testing.T) {
},
file
{
},
file
{
path
:
f
.
path
+
"-innerlink-abs"
,
path
:
f
.
path
+
"-innerlink-abs"
,
linkTarget
:
filepath
.
Join
(
testdir
,
dest
,
"link-target"
),
linkTarget
:
filepath
.
Join
(
testdir
,
dest
,
"link-target"
),
expected
:
""
,
expected
:
mkExpectation
(
f
.
expected
,
"-innerlink-abs"
)
,
},
file
{
},
file
{
path
:
f
.
path
+
"-outerlink"
,
path
:
f
.
path
+
"-outerlink"
,
linkTarget
:
filepath
.
Join
(
backtick
(
f
.
path
),
"link-target"
),
linkTarget
:
filepath
.
Join
(
backtick
(
f
.
path
),
"link-target"
),
expected
:
""
,
expected
:
mkBacktickExpectation
(
f
.
expected
,
"-outerlink"
)
,
},
file
{
},
file
{
path
:
f
.
path
+
"-outerlink-abs"
,
path
:
f
.
path
+
"-outerlink-abs"
,
linkTarget
:
filepath
.
Join
(
testdir
,
"link-target"
),
linkTarget
:
filepath
.
Join
(
testdir
,
"link-target"
),
...
@@ -863,7 +840,19 @@ func TestUntar(t *testing.T) {
...
@@ -863,7 +840,19 @@ func TestUntar(t *testing.T) {
expected
:
filepath
.
Join
(
testdir
,
dest
,
"back-link-second"
),
expected
:
filepath
.
Join
(
testdir
,
dest
,
"back-link-second"
),
},
},
file
{
file
{
path
:
"nested/back-link-first/back-link-second/back-link-term"
,
// This case is chaining together symlinks that step back, so that
// if you just look at the target relative to the path it appears
// inside the destination directory, but if you actually follow each
// step of the path you end up outside the destination directory.
path
:
"nested/back-link-first/back-link-second/back-link-term"
,
linkTarget
:
""
,
expected
:
""
,
})
files
=
append
(
files
,
file
{
// Relative directory path with terminating /
path
:
"direct/dir/"
,
expected
:
""
,
})
})
buf
:=
&
bytes
.
Buffer
{}
buf
:=
&
bytes
.
Buffer
{}
...
@@ -880,8 +869,10 @@ func TestUntar(t *testing.T) {
...
@@ -880,8 +869,10 @@ func TestUntar(t *testing.T) {
Size
:
int64
(
len
(
f
.
path
)),
Size
:
int64
(
len
(
f
.
path
)),
}
}
require
.
NoError
(
t
,
tw
.
WriteHeader
(
hdr
),
f
.
path
)
require
.
NoError
(
t
,
tw
.
WriteHeader
(
hdr
),
f
.
path
)
_
,
err
:=
tw
.
Write
([]
byte
(
f
.
path
))
if
!
strings
.
HasSuffix
(
f
.
path
,
"/"
)
{
require
.
NoError
(
t
,
err
,
f
.
path
)
_
,
err
:=
tw
.
Write
([]
byte
(
f
.
path
))
require
.
NoError
(
t
,
err
,
f
.
path
)
}
}
else
{
}
else
{
hdr
:=
&
tar
.
Header
{
hdr
:=
&
tar
.
Header
{
Name
:
f
.
path
,
Name
:
f
.
path
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment