diff options
Diffstat (limited to 'internal/jail/jail.go')
-rw-r--r-- | internal/jail/jail.go | 55 |
1 files changed, 54 insertions, 1 deletions
diff --git a/internal/jail/jail.go b/internal/jail/jail.go index 16690235..d2b04be0 100644 --- a/internal/jail/jail.go +++ b/internal/jail/jail.go @@ -5,12 +5,18 @@ import ( "io" "os" "path" + "syscall" "time" + + "golang.org/x/sys/unix" ) type pathAndMode struct { path string mode os.FileMode + + // Only respected if mode is os.ModeCharDevice + rdev int } // Jail is a Chroot jail builder @@ -64,7 +70,7 @@ func (j *Jail) Build() error { } for dest, src := range j.files { - if err := copyFile(dest, src.path, src.mode); err != nil { + if err := handleFile(dest, src); err != nil { j.removeAll() return fmt.Errorf("Can't copy %q -> %q. %s", src.path, dest, err) } @@ -102,6 +108,34 @@ func (j *Jail) MkDir(path string, perm os.FileMode) { j.directories = append(j.directories, pathAndMode{path: j.ExternalPath(path), mode: perm}) } +// CharDev enqueues an mknod operation for the given character device at jail +// building time +func (j *Jail) CharDev(path string) error { + fi, err := os.Stat(path) + if err != nil { + return fmt.Errorf("Can't stat %q: %s", path, err) + } + + if (fi.Mode() & os.ModeCharDevice) == 0 { + return fmt.Errorf("Can't mknod %q: not a character device", path) + } + + // Read the device number from the underlying unix implementation of stat() + sys, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("Couldn't determine rdev for %q", path) + } + + jailedDest := j.ExternalPath(path) + j.files[jailedDest] = pathAndMode{ + path: path, + mode: fi.Mode(), + rdev: int(sys.Rdev), + } + + return nil +} + // CopyTo enqueues a file copy operation at jail building time func (j *Jail) CopyTo(dest, src string) error { fi, err := os.Stat(src) @@ -143,6 +177,25 @@ func (j *Jail) ExternalPath(internal string) string { return path.Join(j.Path(), internal) } +func handleFile(dest string, src pathAndMode) error { + // Using `io.Copy` on a character device simply doesn't work + if (src.mode & os.ModeCharDevice) > 0 { + return createCharacterDevice(dest, src) + } + + // FIXME: currently, symlinks, block devices, named pipes and other + // non-regular files will be `Open`ed and have that content streamed to a + // regular file inside the chroot. This is actually desired behaviour for, + // e.g., `/etc/resolv.conf`, but was very surprising + return copyFile(dest, src.path, src.mode) +} + +func createCharacterDevice(dest string, src pathAndMode) error { + unixMode := uint32(src.mode.Perm() | syscall.S_IFCHR) + + return unix.Mknod(dest, unixMode, src.rdev) +} + func copyFile(dest, src string, perm os.FileMode) error { srcFile, err := os.Open(src) if err != nil { |