mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
CI / build (push) Waiting to run
Go / generate (push) Waiting to run
FD 3 arrives without FD_CLOEXEC, which is how it crosses the exec into execd, and execd never set it again, so every command, pty and git it ran held the host's channel: it could read requests meant for the daemon and answer in its name. newDaemon now sets FD_CLOEXEC. The test harness made its pair with SOCK_CLOEXEC, which hid this; the daemon's end now arrives inheritable, as under runsc. The workspace descriptor dup'd for a top-level name closes on exec too. The git ceiling governs only discovery, so -C, --git-dir, --work-tree and a GIT_DIR or GIT_CONFIG_GLOBAL in env reached a repository or a config file outside the workspace. Before its subcommand git now takes -c and options that move nothing, and refuses the rest, since an option execd does not know could take a value that hides the next. GIT_* in env is refused and the global config is /dev/null, so HOME picks no file. The ceiling test now covers each of these. Co-authored-by: Hanzo Dev <[email protected]>
134 lines
3.9 KiB
Go
134 lines
3.9 KiB
Go
// Copyright 2026 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// root is the workspace. Every path in a request is resolved relative to it
|
|
// with RESOLVE_BENEATH, so the kernel — not a string check — rejects an
|
|
// absolute path, a ".." that would leave the workspace, and a symlink that
|
|
// points out of it. A ".." that stays inside resolves normally.
|
|
type root struct {
|
|
fd int // O_PATH descriptor of the workspace directory
|
|
name string // its canonical absolute path
|
|
}
|
|
|
|
const resolve = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS
|
|
|
|
func openRoot(dir string) (*root, error) {
|
|
fd, err := unix.Open(dir, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open workspace %s: %w", dir, err)
|
|
}
|
|
name, err := readFd(fd)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return nil, err
|
|
}
|
|
return &root{fd: fd, name: name}, nil
|
|
}
|
|
|
|
func (r *root) close() error { return unix.Close(r.fd) }
|
|
|
|
// open resolves p beneath the workspace and returns an open descriptor.
|
|
func (r *root) open(p string, flags int, mode uint32) (int, error) {
|
|
return r.openAt(r.fd, p, flags, mode)
|
|
}
|
|
|
|
func (r *root) openAt(dirfd int, p string, flags int, mode uint32) (int, error) {
|
|
if p == "" {
|
|
p = "."
|
|
}
|
|
how := &unix.OpenHow{
|
|
Flags: uint64(flags) | unix.O_CLOEXEC,
|
|
Mode: uint64(mode),
|
|
Resolve: resolve,
|
|
}
|
|
fd, err := unix.Openat2(dirfd, p, how)
|
|
if err != nil {
|
|
return -1, fmt.Errorf("%s: %w", p, err)
|
|
}
|
|
return fd, nil
|
|
}
|
|
|
|
// file resolves p and hands back an *os.File that owns the descriptor.
|
|
func (r *root) file(p string, flags int, mode uint32) (*os.File, error) {
|
|
fd, err := r.open(p, flags, mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return os.NewFile(uintptr(fd), path.Join(r.name, p)), nil
|
|
}
|
|
|
|
// abs resolves p beneath the workspace and returns its canonical absolute
|
|
// path. Used where the kernel interface takes a path and not a descriptor:
|
|
// a child's working directory, an inotify watch.
|
|
func (r *root) abs(p string) (string, error) {
|
|
fd, err := r.open(p, unix.O_PATH, 0)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer unix.Close(fd)
|
|
name, err := readFd(fd)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if name != r.name && !strings.HasPrefix(name, r.name+"/") {
|
|
return "", fmt.Errorf("%s: resolves outside the workspace", p)
|
|
}
|
|
return name, nil
|
|
}
|
|
|
|
// parent resolves p's directory beneath the workspace and returns its
|
|
// descriptor together with p's last component, which is what an atomic
|
|
// create-then-rename needs. The directory is handed to the kernel as written,
|
|
// never cleaned first: "away/../f" through a symlink out of the tree has to
|
|
// mean here what it means to a read.
|
|
func (r *root) parent(p string) (int, string, error) {
|
|
dir, base := path.Split(p)
|
|
if base == "" || base == "." || base == ".." {
|
|
return -1, "", fmt.Errorf("%s: not a file name", p)
|
|
}
|
|
if dir == "" {
|
|
fd, err := unix.FcntlInt(uintptr(r.fd), unix.F_DUPFD_CLOEXEC, 0)
|
|
if err != nil {
|
|
return -1, "", err
|
|
}
|
|
return fd, base, nil
|
|
}
|
|
fd, err := r.open(strings.TrimSuffix(dir, "/"), unix.O_PATH|unix.O_DIRECTORY, 0)
|
|
if err != nil {
|
|
return -1, "", err
|
|
}
|
|
return fd, base, nil
|
|
}
|
|
|
|
func readFd(fd int) (string, error) {
|
|
name, err := os.Readlink(fmt.Sprintf("/proc/self/fd/%d", fd))
|
|
if err != nil {
|
|
return "", fmt.Errorf("read /proc/self/fd/%d: %w", fd, err)
|
|
}
|
|
return name, nil
|
|
}
|