1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
package copier
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/reexec"
"github.com/moby/sys/capability"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func init() {
reexec.Register("get", getWrappedMain)
}
type getWrappedOptions struct {
Root, Directory string
GetOptions GetOptions
Globs []string
DropCaps []capability.Cap
}
func getWrapped(root string, directory string, getOptions GetOptions, globs []string, dropCaps []capability.Cap, bulkWriter io.Writer) error {
options := getWrappedOptions{
Root: root,
Directory: directory,
GetOptions: getOptions,
Globs: globs,
DropCaps: dropCaps,
}
encoded, err := json.Marshal(&options)
if err != nil {
return fmt.Errorf("marshalling options: %w", err)
}
cmd := reexec.Command("get")
cmd.Env = append(cmd.Env, "OPTIONS="+string(encoded))
cmd.Stdout = bulkWriter
stderrBuf := bytes.Buffer{}
cmd.Stderr = &stderrBuf
err = cmd.Run()
if stderrBuf.Len() > 0 {
if err != nil {
return fmt.Errorf("%v: %s", err, stderrBuf.String())
}
return fmt.Errorf("%s", stderrBuf.String())
}
return err
}
func getWrappedMain() {
var options getWrappedOptions
if err := json.Unmarshal([]byte(os.Getenv("OPTIONS")), &options); err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(1)
}
if len(options.DropCaps) > 0 {
caps, err := capability.NewPid2(0)
if err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(1)
}
if err := caps.Load(); err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(1)
}
for _, capType := range []capability.CapType{
capability.AMBIENT,
capability.BOUNDING,
capability.INHERITABLE,
capability.PERMITTED,
capability.EFFECTIVE,
} {
for _, cap := range options.DropCaps {
if caps.Get(capType, cap) {
caps.Unset(capType, cap)
}
}
if err := caps.Apply(capType); err != nil {
fmt.Fprintf(os.Stderr, "error dropping capability %+v: %v", options.DropCaps, err)
os.Exit(1)
}
}
}
if err := Get(options.Root, options.Directory, options.GetOptions, options.Globs, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(1)
}
}
func TestGetPermissionErrorNoChroot(t *testing.T) {
couldChroot := canChroot
canChroot = false
testGetPermissionError(t)
canChroot = couldChroot
}
func TestGetPermissionErrorChroot(t *testing.T) {
if uid != 0 {
t.Skipf("chroot() requires root privileges, skipping")
}
couldChroot := canChroot
canChroot = true
testGetPermissionError(t)
canChroot = couldChroot
}
func testGetPermissionError(t *testing.T) {
dropCaps := []capability.Cap{capability.CAP_DAC_OVERRIDE, capability.CAP_DAC_READ_SEARCH}
tmp := t.TempDir()
err := os.Mkdir(filepath.Join(tmp, "unreadable-directory"), 0o000)
require.NoError(t, err, "error creating an unreadable directory")
err = os.Mkdir(filepath.Join(tmp, "readable-directory"), 0o755)
require.NoError(t, err, "error creating a readable directory")
err = os.Mkdir(filepath.Join(tmp, "readable-directory", "unreadable-subdirectory"), 0o000)
require.NoError(t, err, "error creating an unreadable subdirectory")
err = os.WriteFile(filepath.Join(tmp, "unreadable-file"), []byte("hi, i'm a file that you can't read"), 0o000)
require.NoError(t, err, "error creating an unreadable file")
err = os.WriteFile(filepath.Join(tmp, "readable-file"), []byte("hi, i'm also a file, and you can read me"), 0o644)
require.NoError(t, err, "error creating a readable file")
err = os.WriteFile(filepath.Join(tmp, "readable-directory", "unreadable-file"), []byte("hi, i'm also a file that you can't read"), 0o000)
require.NoError(t, err, "error creating an unreadable file in a readable directory")
for _, ignore := range []bool{false, true} {
t.Run(fmt.Sprintf("ignore=%v", ignore), func(t *testing.T) {
var buf bytes.Buffer
err = getWrapped(tmp, tmp, GetOptions{IgnoreUnreadable: ignore}, []string{"."}, dropCaps, &buf)
if ignore {
assert.NoError(t, err, "expected no errors")
tr := tar.NewReader(&buf)
items := 0
_, err := tr.Next()
for err == nil {
items++
_, err = tr.Next()
}
assert.True(t, errors.Is(err, io.EOF), "expected EOF to finish read contents")
assert.Equalf(t, 2, items, "expected two readable items, got %d", items)
} else {
assert.Error(t, err, "expected an error")
assert.Truef(t, errorIsPermission(err), "expected the error (%v) to be a permission error", err)
}
})
}
}
func TestGetNoCrossDevice(t *testing.T) {
if uid != 0 {
t.Skip("test requires root privileges, skipping")
}
tmpdir := t.TempDir()
err := unix.Unshare(unix.CLONE_NEWNS)
require.NoError(t, err, "error creating new mount namespace")
subdir := filepath.Join(tmpdir, "subdir")
err = os.Mkdir(subdir, 0o755)
require.NoErrorf(t, err, "error creating %q", subdir)
err = mount.Mount("tmpfs", subdir, "tmpfs", "rw")
require.NoErrorf(t, err, "error mounting tmpfs at %q", subdir)
defer func() {
err := mount.Unmount(subdir)
assert.NoErrorf(t, err, "error unmounting %q", subdir)
}()
skipped := filepath.Join(subdir, "skipped.txt")
err = os.WriteFile(skipped, []byte("this file should have been skipped\n"), 0o644)
require.NoErrorf(t, err, "error writing file at %q", skipped)
var buf bytes.Buffer
err = Get(tmpdir, tmpdir, GetOptions{NoCrossDevice: true}, []string{"/"}, &buf) // grab contents of tmpdir
require.NoErrorf(t, err, "error reading contents at %q", tmpdir)
tr := tar.NewReader(&buf)
th, err := tr.Next() // should be the "subdir" directory
require.NoError(t, err, "error reading first entry archived")
assert.Equal(t, "subdir", th.Name, `first entry in archive was not named "subdir"`)
th, err = tr.Next()
assert.Error(t, err, "should not have gotten a second entry in archive")
assert.True(t, errors.Is(err, io.EOF), "expected an EOF trying to read a second entry in archive")
if err == nil {
t.Logf("got unexpected entry for %q", th.Name)
}
}
|