// MountGnupg bind mounts $GNUPGHOME or ~/.gnupg and the agent socket // if available. The agent is ignored if the home dir isn't available. func (e *enter) MountGnupg() error { origHome := os.Getenv("GNUPGHOME") if origHome == "" { origHome = filepath.Join(e.User.HomeDir, ".gnupg") } if _, err := os.Stat(origHome); err != nil { // Skip but do not pass along $GNUPGHOME return os.Unsetenv("GNUPGHOME") } newHome, err := ioutil.TempDir(e.UserRunDir, "gnupg-") if err != nil { return err } if err := system.Bind(origHome, newHome); err != nil { return err } chrootHome := strings.TrimPrefix(newHome, e.Chroot) if err := os.Setenv("GNUPGHOME", chrootHome); err != nil { return err } return e.MountAgent("GPG_AGENT_INFO") }
// MountAgent bind mounts a SSH or GnuPG agent socket into the chroot func (e *enter) MountAgent(env string) error { origPath := os.Getenv(env) if origPath == "" { return nil } origDir, origFile := filepath.Split(origPath) if _, err := os.Stat(origDir); err != nil { // Just skip if the agent has gone missing. return nil } newDir, err := ioutil.TempDir(e.UserRunDir, "agent-") if err != nil { return err } if err := system.Bind(origDir, newDir); err != nil { return err } newPath := filepath.Join(newDir, origFile) chrootPath := strings.TrimPrefix(newPath, e.Chroot) return os.Setenv(env, chrootPath) }
// MountAPI mounts standard Linux API filesystems. // When possible the filesystems are mounted read-only. func (e *enter) MountAPI() error { var apis = []struct { Path string Type string Opts string }{ {"/proc", "proc", "ro,nosuid,nodev,noexec"}, {"/sys", "sysfs", "ro,nosuid,nodev,noexec"}, {"/run", "tmpfs", "nosuid,nodev,mode=755"}, } for _, fs := range apis { target := filepath.Join(e.Chroot, fs.Path) if err := system.Mount("", target, fs.Type, fs.Opts); err != nil { return err } } // Since loop devices are dynamic we need the host's managed /dev if err := system.ReadOnlyBind("/dev", filepath.Join(e.Chroot, "dev")); err != nil { return err } // /dev/pts must be read-write because emerge chowns tty devices. if err := system.Bind("/dev/pts", filepath.Join(e.Chroot, "dev/pts")); err != nil { return err } // Unfortunately using the host's /dev complicates /dev/shm which may // be a directory or a symlink into /run depending on the distro. :( // XXX: catalyst does not work on systems with a /dev/shm symlink! if system.IsSymlink("/dev/shm") { shmPath, err := filepath.EvalSymlinks("/dev/shm") if err != nil { return err } // Only accept known values to avoid surprises. if shmPath != "/run/shm" { return fmt.Errorf("Unexpected shm path: %s", shmPath) } newPath := filepath.Join(e.Chroot, shmPath) if err := os.Mkdir(newPath, 01777); err != nil { return err } if err := os.Chmod(newPath, 01777); err != nil { return err } } else { shmPath := filepath.Join(e.Chroot, "dev/shm") if err := system.Mount("", shmPath, "tmpfs", "nosuid,nodev"); err != nil { return err } } return nil }
// bind mount the repo source tree into the chroot and run a command func enterChrootHelper(args []string) (err error) { if len(args) < 3 { return fmt.Errorf("got %d args, need at least 3", len(args)) } e := enter{ RepoRoot: args[0], Chroot: args[1], Cmd: args[2:], } username := os.Getenv("SUDO_USER") if username == "" { return fmt.Errorf("SUDO_USER environment variable is not set.") } if e.User, err = user.Lookup(username); err != nil { return err } e.UserRunDir = filepath.Join(e.Chroot, "run", "user", e.User.Uid) newRepoRoot := filepath.Join(e.Chroot, chrootRepoRoot) if err := os.MkdirAll(newRepoRoot, 0755); err != nil { return err } // Only copy if resolv.conf exists, if missing resolver uses localhost resolv := "/etc/resolv.conf" if _, err := os.Stat(resolv); err == nil { chrootResolv := filepath.Join(e.Chroot, resolv) if err := system.InstallRegularFile(resolv, chrootResolv); err != nil { return err } } // namespaces are per-thread attributes runtime.LockOSThread() defer runtime.UnlockOSThread() if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil { return fmt.Errorf("Unsharing mount namespace failed: %v", err) } if err := system.RecursiveSlave("/"); err != nil { return err } if err := system.Bind(e.RepoRoot, newRepoRoot); err != nil { return err } if err := e.MountAPI(); err != nil { return err } if err = os.MkdirAll(e.UserRunDir, 0755); err != nil { return err } if err = os.Chown(e.UserRunDir, e.User.UidNo, e.User.GidNo); err != nil { return err } if err := e.MountAgent("SSH_AUTH_SOCK"); err != nil { return err } if err := e.MountGnupg(); err != nil { return err } if err := e.CopyGoogleCreds(); err != nil { return err } if err := syscall.Chroot(e.Chroot); err != nil { return fmt.Errorf("Chrooting to %q failed: %v", e.Chroot, err) } if err := os.Chdir(chrootRepoRoot); err != nil { return err } sudo := "/usr/bin/sudo" sudoArgs := append([]string{sudo, "-u", username, "--"}, e.Cmd...) return syscall.Exec(sudo, sudoArgs, os.Environ()) }