Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Thomas <nick@gitlab.com>2018-04-25 18:25:48 +0300
committerNick Thomas <nick@gitlab.com>2018-04-27 19:23:39 +0300
commitca27f8df7263232c00f2cc0c476408feceb9c9f6 (patch)
tree356d953c908c1c834c794bd0d519bae99b5b2bf1
parentfef9c6546b3ce00234db349dac2eabce1ffd2b40 (diff)
Create /dev/random and /dev/urandom when daemonizing and jailing
Go on Linux normally uses the getrandom() syscall to get entropy. However, this is onl available for Linux version >= 3.17 Making /dev/random and /dev/urandom available in the chrooted jail allows the daemon to operate correctly on these systems. It's important to handle this automatically, as the previous workaround of manually creating these character devices is made more difficult by the use of a random directory in $TMPDIR.
-rw-r--r--daemon.go11
-rw-r--r--internal/jail/jail.go55
-rw-r--r--internal/jail/jail_test.go37
3 files changed, 102 insertions, 1 deletions
diff --git a/daemon.go b/daemon.go
index 9a732709..661ca965 100644
--- a/daemon.go
+++ b/daemon.go
@@ -110,6 +110,17 @@ func daemonChroot(cmd *exec.Cmd) (*jail.Jail, error) {
return nil, err
}
+ // Add /dev/urandom and /dev/random inside the jail. This is required to
+ // support Linux versions < 3.17, which do not have the getrandom() syscall
+ cage.MkDir("/dev", 0755)
+ if err := cage.CharDev("/dev/urandom"); err != nil {
+ return nil, err
+ }
+
+ if err := cage.CharDev("/dev/random"); err != nil {
+ return nil, err
+ }
+
// Add gitlab-pages inside the jail
err = cage.CopyTo("/gitlab-pages", cmd.Path)
if err != nil {
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 {
diff --git a/internal/jail/jail_test.go b/internal/jail/jail_test.go
index 6c96ceeb..6de870df 100644
--- a/internal/jail/jail_test.go
+++ b/internal/jail/jail_test.go
@@ -6,6 +6,7 @@ import (
"os"
"path"
"runtime"
+ "syscall"
"testing"
"time"
@@ -113,6 +114,42 @@ func TestJailDisposeDoNotFailOnMissingPath(t *testing.T) {
assert.NoError(err)
}
+func TestJailWithCharacterDevice(t *testing.T) {
+ if os.Geteuid() != 0 {
+ t.Log("This test only works if run as root")
+ t.SkipNow()
+ }
+
+ // Determine the expected rdev
+ fi, err := os.Stat("/dev/urandom")
+ require.NoError(t, err)
+ sys, ok := fi.Sys().(*syscall.Stat_t)
+ if !ok {
+ t.Log("Couldn't determine expected rdev for /dev/urandom, skipping")
+ t.SkipNow()
+ }
+
+ expectedRdev := sys.Rdev
+
+ jailPath := tmpJailPath()
+ cage := jail.New(jailPath, 0755)
+ cage.MkDir("/dev", 0755)
+
+ require.NoError(t, cage.CharDev("/dev/urandom"))
+ require.NoError(t, cage.Build())
+ defer cage.Dispose()
+
+ fi, err = os.Lstat(path.Join(cage.Path(), "/dev/urandom"))
+ require.NoError(t, err)
+
+ isCharDev := fi.Mode()&os.ModeCharDevice == os.ModeCharDevice
+ assert.True(t, isCharDev, "Created file was not a character device")
+
+ sys, ok = fi.Sys().(*syscall.Stat_t)
+ require.True(t, ok, "Couldn't determine rdev of created character device")
+ assert.Equal(t, expectedRdev, sys.Rdev, "Incorrect rdev for /dev/urandom")
+}
+
func TestJailWithFiles(t *testing.T) {
tests := []struct {
name string