From 24c10ea918d122a0f138bae2bb1c820c385ae06b Mon Sep 17 00:00:00 2001 From: Rex Ren Date: Tue, 28 Jul 2026 17:28:12 -0700 Subject: [PATCH] runsc/bwrap: Expand namespace flags in bwrap personality Add `--unshare-cgroup` flag Refactor `--share-net`, `--unshare-all` flags Fix support for network args for both `--unshare-net` and `--share-net` PiperOrigin-RevId: 955562044 --- runsc/cmd/alias/bwrap/bwrap.go | 311 +++++++++++++++++- .../cmd/alias/bwrap/bwrap_integration_test.go | 157 +++++++++ runsc/cmd/alias/bwrap/bwrap_test.go | 90 ++++- runsc/cmd/alias/bwrap/cli.go | 37 ++- 4 files changed, 586 insertions(+), 9 deletions(-) diff --git a/runsc/cmd/alias/bwrap/bwrap.go b/runsc/cmd/alias/bwrap/bwrap.go index bd6d3ea39fe..b217c7d782b 100644 --- a/runsc/cmd/alias/bwrap/bwrap.go +++ b/runsc/cmd/alias/bwrap/bwrap.go @@ -17,9 +17,12 @@ package bwrap import ( "encoding/json" + "errors" "fmt" "math/rand" + "net" "os" + "os/exec" "path/filepath" "strconv" "strings" @@ -97,6 +100,9 @@ func (c *bwrapConfig) runscDo(spec *specs.Spec, workspaceDir string, conf *confi return subcommands.ExitSuccess } +// errNoDefaultInterface is returned when no default network interface is found +var errNoDefaultInterface = errors.New("no default interface found") + // do executes the container. func do(c *bwrapConfig, waitStatus *unix.WaitStatus) subcommands.ExitStatus { // Create a temporary directory for the bwrap runtime files. @@ -107,15 +113,20 @@ func do(c *bwrapConfig, waitStatus *unix.WaitStatus) subcommands.ExitStatus { defer os.RemoveAll(workspaceDir) c.WorkspaceDir = workspaceDir - // Build the runsc spec from the bwrap config. + cid := fmt.Sprintf("runsc-%06d", rand.Int31n(1000000)) - spec, err := c.buildRunscSpec() + // Build the runsc spec from the bwrap config. + spec, err := c.buildRunscSpec(cid) if err != nil { return util.Errorf("failed to build runsc spec: %v", err) } + if c.cleanNet != nil { + defer c.cleanNet() + } + // Run the container. - return c.runscDo(spec, c.WorkspaceDir, c.runscConfig, generateUID(), waitStatus) + return c.runscDo(spec, c.WorkspaceDir, c.runscConfig, cid, waitStatus) } // MountOpType represents the type of mount operation. @@ -165,9 +176,26 @@ func (c *bwrapConfig) newMountOp(src, dst string, mountType MountOpType) (*Mount }, nil } +// CapOpType represents the type of capability operation. +type CapOpType string + +const ( + // CapOpDrop represents a capability drop operation. + CapOpDrop CapOpType = "drop" + // CapOpAdd represents a capability add operation. + CapOpAdd CapOpType = "add" +) + +// CapOp represents a capability operation. +type CapOp struct { + Type CapOpType + Cap string +} + // bwrapConfig represents the configuration for the bwrap sandbox. type bwrapConfig struct { Mounts []*MountOp + CapOps []*CapOp UnshareNet bool Args []string Chdir string @@ -179,7 +207,8 @@ type bwrapConfig struct { GID int UnshareUser bool Hostname string - ShareNet bool + cleanNet func() + ip string } // String returns a string representation of the bwrapConfig. @@ -261,7 +290,7 @@ func (c *bwrapConfig) mapCWD() (string, error) { // buildRunscSpec builds the runsc Spec from the bwrapConfig. // TODO: b/508701483 - Use the causeway library when it is ready // and update this function. -func (c *bwrapConfig) buildRunscSpec() (*specs.Spec, error) { +func (c *bwrapConfig) buildRunscSpec(cid string) (*specs.Spec, error) { if c.UID != -1 && !c.UnshareUser { return nil, fmt.Errorf("bwrap: Specifying --uid requires --unshare-user") } @@ -364,16 +393,44 @@ func (c *bwrapConfig) buildRunscSpec() (*specs.Spec, error) { } } + for _, capOp := range c.CapOps { + normCap, err := normalizeCap(capOp.Cap) + if err != nil { + return nil, err + } + switch capOp.Type { + case CapOpAdd: + addCapability(spec.Process.Capabilities, normCap) + case CapOpDrop: + dropCapability(spec.Process.Capabilities, normCap) + } + } + if c.Hostname != "" { spec.Hostname = c.Hostname } - // TODO: b/508701483 - Fix support for network args. if c.UnshareNet { + if c.runscConfig != nil { + c.runscConfig.Network = config.NetworkNone + } if spec.Linux == nil { spec.Linux = &specs.Linux{} } spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace}) + } else { + switch clean, err := c.setupNet(cid, spec); err { + case errNoDefaultInterface: + log.Warningf("Network interface not found, using internal network") + if spec.Linux == nil { + spec.Linux = &specs.Linux{} + } + spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace}) + case nil: + c.cleanNet = clean + default: + return nil, fmt.Errorf("error setting up network: %v", err) + } } // Set the current working directory. @@ -467,3 +524,245 @@ func (c *bwrapConfig) resolveEnv() { } c.Env = env } + +func normalizeCap(capName string) (string, error) { + normCap := strings.ToUpper(strings.TrimSpace(capName)) + if normCap != "ALL" { + if !strings.HasPrefix(normCap, "CAP_") { + normCap = "CAP_" + normCap + } + if !isKnownCapability(normCap) { + return "", fmt.Errorf("bwrap: unknown cap: %s", capName) + } + } + return normCap, nil +} + +// isKnownCapability checks if capName is a valid Linux capability known to gVisor. +func isKnownCapability(capName string) bool { + for _, c := range specutils.AllCapabilities().Bounding { + if c == capName { + return true + } + } + return false +} + +// addCapability adds a single capability to all 5 capability sets. +func addCapability(caps *specs.LinuxCapabilities, capName string) { + addUnique := func(set []string) []string { + for _, c := range set { + if c == capName { + return set + } + } + return append(set, capName) + } + caps.Bounding = addUnique(caps.Bounding) + caps.Effective = addUnique(caps.Effective) + caps.Inheritable = addUnique(caps.Inheritable) + caps.Permitted = addUnique(caps.Permitted) + caps.Ambient = addUnique(caps.Ambient) +} + +func dropCapability(caps *specs.LinuxCapabilities, capName string) { + if capName == "ALL" { + caps.Bounding = nil + caps.Effective = nil + caps.Inheritable = nil + caps.Permitted = nil + caps.Ambient = nil + return + } + specutils.DropCapability(caps, capName) +} + +func defaultDevice() (string, error) { + out, err := exec.Command("ip", "route", "list", "default").CombinedOutput() + if err != nil { + return "", err + } + parts := strings.Fields(string(out)) + if len(parts) < 5 { + return "", fmt.Errorf("malformed %q output: %q", "ip route list default", string(out)) + } + return parts[4], nil +} + +func deviceMTU(dev string) (int, error) { + intf, err := net.InterfaceByName(dev) + if err != nil { + return 0, err + } + return intf.MTU, nil +} + +func calculatePeerIP(ip string) (string, error) { + parts := strings.Split(ip, ".") + if len(parts) != 4 { + return "", fmt.Errorf("invalid IP format %q", ip) + } + n, err := strconv.Atoi(parts[3]) + if err != nil { + return "", fmt.Errorf("invalid IP format %q: %v", ip, err) + } + n++ + if n > 255 { + n = 1 + } + return fmt.Sprintf("%s.%s.%s.%d", parts[0], parts[1], parts[2], n), nil +} + +func deviceNames(cid string) (string, string) { + // Device name is limited to 15 letters. + return "ve-" + cid, "vp-" + cid +} + +func makeFile(dest, content string, spec *specs.Spec) (string, error) { + tmpFile, err := os.CreateTemp("", filepath.Base(dest)) + if err != nil { + return "", err + } + defer tmpFile.Close() + if _, err := tmpFile.WriteString(content); err != nil { + if err := os.Remove(tmpFile.Name()); err != nil { + log.Warningf("Failed to remove %q: %v", tmpFile.Name(), err) + } + return "", err + } + spec.Mounts = append(spec.Mounts, specs.Mount{ + Source: tmpFile.Name(), + Destination: dest, + Type: "bind", + Options: []string{"ro"}, + }) + return tmpFile.Name(), nil +} + +func tryRemove(path string) { + if path == "" { + return + } + + if err := os.Remove(path); err != nil { + log.Warningf("Failed to remove %q: %v", path, err) + } +} + +// cleanupNet tries to cleanup the network setup in setupNet. +// +// It may be called when setupNet is only partially complete, in which case it +// will cleanup as much as possible, logging warnings for the rest. +// +// Unfortunately none of this can be automatically cleaned up on process exit, +// we must do so explicitly. +func (c *bwrapConfig) cleanupNet(cid, dev, resolvPath, hostnamePath, hostsPath string) { + _, peer := deviceNames(cid) + ip := c.ip + + cmds := []string{ + fmt.Sprintf("ip link delete %s", peer), + fmt.Sprintf("ip netns delete %s", cid), + fmt.Sprintf("iptables -t nat -D POSTROUTING -s %s -o %s -m comment --comment runsc-%s -j MASQUERADE", ip, dev, peer), + fmt.Sprintf("iptables -D FORWARD -i %s -o %s -j ACCEPT", dev, peer), + fmt.Sprintf("iptables -D FORWARD -o %s -i %s -j ACCEPT", dev, peer), + } + + for _, cmd := range cmds { + log.Debugf("Run %q", cmd) + args := strings.Fields(cmd) + execCmd := exec.Command(args[0], args[1:]...) + if err := execCmd.Run(); err != nil { + log.Warningf("Failed to run %q: %v", cmd, err) + } + } + + tryRemove(resolvPath) + tryRemove(hostnamePath) + tryRemove(hostsPath) +} + +func addNamespace(spec *specs.Spec, ns specs.LinuxNamespace) { + if spec.Linux == nil { + spec.Linux = &specs.Linux{} + } + spec.Linux.Namespaces = append(spec.Linux.Namespaces, ns) +} + +// setupNet setups up the sandbox network, including the creation of a network +// namespace, and iptable rules to redirect the traffic. Returns a cleanup +// function to tear down the network. Returns errNoDefaultInterface when there +// is no network interface available to setup the network. +func (c *bwrapConfig) setupNet(cid string, spec *specs.Spec) (func(), error) { + ip := c.ip + dev, err := defaultDevice() + if err != nil { + return nil, errNoDefaultInterface + } + mtu, err := deviceMTU(dev) + if err != nil { + return nil, err + } + peerIP, err := calculatePeerIP(ip) + if err != nil { + return nil, err + } + veth, peer := deviceNames(cid) + + cmds := []string{ + fmt.Sprintf("ip link add %s mtu %v type veth peer name %s", veth, mtu, peer), + + // Setup device outside the namespace. + fmt.Sprintf("ip addr add %s/24 dev %s", peerIP, peer), + fmt.Sprintf("ip link set %s up", peer), + + // Setup device inside the namespace. + fmt.Sprintf("ip netns add %s", cid), + fmt.Sprintf("ip link set %s netns %s", veth, cid), + fmt.Sprintf("ip netns exec %s ip addr add %s/24 dev %s", cid, ip, veth), + fmt.Sprintf("ip netns exec %s ip link set %s up", cid, veth), + fmt.Sprintf("ip netns exec %s ip link set lo up", cid), + fmt.Sprintf("ip netns exec %s ip route add default via %s", cid, peerIP), + + // Enable network access. + "sysctl -w net.ipv4.ip_forward=1", + fmt.Sprintf("iptables -t nat -A POSTROUTING -s %s -o %s -m comment --comment runsc-%s -j MASQUERADE", ip, dev, peer), + fmt.Sprintf("iptables -A FORWARD -i %s -o %s -j ACCEPT", dev, peer), + fmt.Sprintf("iptables -A FORWARD -o %s -i %s -j ACCEPT", dev, peer), + } + + for _, cmd := range cmds { + log.Debugf("Run %q", cmd) + args := strings.Fields(cmd) + execCmd := exec.Command(args[0], args[1:]...) + if err := execCmd.Run(); err != nil { + c.cleanupNet(cid, dev, "", "", "") + return nil, fmt.Errorf("failed to run %q: %v", cmd, err) + } + } + + resolvPath, err := makeFile("/etc/resolv.conf", "nameserver 8.8.8.8\n", spec) + if err != nil { + c.cleanupNet(cid, dev, "", "", "") + return nil, err + } + hostnamePath, err := makeFile("/etc/hostname", cid+"\n", spec) + if err != nil { + c.cleanupNet(cid, dev, resolvPath, "", "") + return nil, err + } + hosts := fmt.Sprintf("127.0.0.1\tlocalhost\n%s\t%s\n", ip, cid) + hostsPath, err := makeFile("/etc/hosts", hosts, spec) + if err != nil { + c.cleanupNet(cid, dev, resolvPath, hostnamePath, "") + return nil, err + } + + netns := specs.LinuxNamespace{ + Type: specs.NetworkNamespace, + Path: filepath.Join("/var/run/netns", cid), + } + addNamespace(spec, netns) + + return func() { c.cleanupNet(cid, dev, resolvPath, hostnamePath, hostsPath) }, nil +} diff --git a/runsc/cmd/alias/bwrap/bwrap_integration_test.go b/runsc/cmd/alias/bwrap/bwrap_integration_test.go index e531ab0d930..a74423ac3f5 100644 --- a/runsc/cmd/alias/bwrap/bwrap_integration_test.go +++ b/runsc/cmd/alias/bwrap/bwrap_integration_test.go @@ -348,3 +348,160 @@ func TestProc(t *testing.T) { }) } } + +func TestCapabilities(t *testing.T) { + if err := testutil.ConfigureExePath(); err != nil { + t.Fatalf("failed to configure exe path: %v", err) + } + + stop := testutil.StartReaper() + defer stop() + + rootDir := t.TempDir() + + tests := []struct { + name string + bwrapArgs []string + wantOutput string + }{ + { + name: "CapDropAll", + bwrapArgs: []string{ + "--unshare-all", + "--ro-bind", "/", "/", + "--cap-drop", "ALL", + "--", + "/bin/cat", "/proc/self/status", + }, + // All bits zeroed out after dropping ALL capabilities. + wantOutput: "CapEff:\t0000000000000000", + }, + { + name: "CapDropAllAndAddNetAdmin", + bwrapArgs: []string{ + "--unshare-all", + "--ro-bind", "/", "/", + "--cap-drop", "ALL", + "--cap-add", "NET_ADMIN", + "--", + "/bin/cat", "/proc/self/status", + }, + // CAP_NET_ADMIN is bit 12 (1 << 12 = 0x1000). + wantOutput: "CapEff:\t0000000000001000", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runRootDir := filepath.Join(rootDir, tc.name) + if err := os.MkdirAll(runRootDir, 0755); err != nil { + t.Fatalf("creating root dir: %v", err) + } + + args := append([]string{ + "--root", runRootDir, + "bwrap", + }, tc.bwrapArgs...) + + cmd := exec.Command(specutils.ExePath, args...) + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + t.Fatalf("runsc bwrap failed: %v\nStderr: %s", err, stderr.String()) + } + + output := strings.TrimSpace(stdout.String()) + if tc.wantOutput != "" && !strings.Contains(output, tc.wantOutput) { + t.Errorf("output missing expected capability mask:\ngot:\n%s\nwant to contain: %q", output, tc.wantOutput) + } + }) + } +} + +func TestNamespaces(t *testing.T) { + if err := testutil.ConfigureExePath(); err != nil { + t.Fatalf("failed to configure exe path: %v", err) + } + + stop := testutil.StartReaper() + defer stop() + + rootDir := t.TempDir() + + tests := []struct { + name string + bwrapArgs []string + wantOutput string + }{ + { + name: "UnshareAll", + bwrapArgs: []string{ + "--unshare-all", + "--ro-bind", "/", "/", + "--", + "/usr/bin/id", "-u", + }, + wantOutput: fmt.Sprintf("%d", os.Getuid()), + }, + { + name: "UnshareCgroup", + bwrapArgs: []string{ + "--unshare-cgroup", + "--ro-bind", "/", "/", + "--", + "/usr/bin/id", "-u", + }, + wantOutput: fmt.Sprintf("%d", os.Getuid()), + }, + { + name: "UnshareNet", + bwrapArgs: []string{ + "--unshare-net", + "--ro-bind", "/", "/", + "--", + "/bin/cat", "/proc/net/dev", + }, + wantOutput: "lo", + }, + { + name: "ShareNet", + bwrapArgs: []string{ + "--share-net", + "--ro-bind", "/", "/", + "--", + "/bin/cat", "/proc/net/dev", + }, + wantOutput: "ve-runsc", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runRootDir := filepath.Join(rootDir, tc.name) + if err := os.MkdirAll(runRootDir, 0755); err != nil { + t.Fatalf("creating root dir: %v", err) + } + + args := append([]string{ + "--root", runRootDir, + "bwrap", + }, tc.bwrapArgs...) + + cmd := exec.Command(specutils.ExePath, args...) + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + t.Fatalf("runsc bwrap failed: %v\nStderr: %s", err, stderr.String()) + } + + output := strings.TrimSpace(stdout.String()) + if tc.wantOutput != "" && !strings.Contains(output, tc.wantOutput) { + t.Errorf("output = %q, want it to contain %q", output, tc.wantOutput) + } + }) + } +} diff --git a/runsc/cmd/alias/bwrap/bwrap_test.go b/runsc/cmd/alias/bwrap/bwrap_test.go index 791784338cf..5a7304779a9 100644 --- a/runsc/cmd/alias/bwrap/bwrap_test.go +++ b/runsc/cmd/alias/bwrap/bwrap_test.go @@ -111,6 +111,9 @@ func TestBuildRunscSpec(t *testing.T) { Args: []string{"ls"}, Cwd: "/", // No mounts, so the cwd is set to the /. }, + Linux: &specs.Linux{ + Namespaces: []specs.LinuxNamespace{{Type: specs.NetworkNamespace}}, + }, Mounts: appendMounts( // A tmpfs `/` is created in case no root is passed. []*specs.Mount{{Type: "tmpfs", Destination: "/"}}, @@ -127,7 +130,11 @@ func TestBuildRunscSpec(t *testing.T) { t.Run(tc.name, func(t *testing.T) { cfg := tc.cfg cfg.WorkspaceDir = workspaceDir - gotSpec, err := tc.cfg.buildRunscSpec() + cfg.ip = "192.168.10.2" + gotSpec, err := tc.cfg.buildRunscSpec("test-cid") + if tc.cfg.cleanNet != nil { + defer tc.cfg.cleanNet() + } if err != nil { t.Fatalf("BuildSpec failed: %v", err) } @@ -369,6 +376,50 @@ func TestParseFlags(t *testing.T) { Args: []string{"bash"}, }, }, + { + name: "UnshareCgroup", + args: []string{"--unshare-cgroup", "bash"}, + wantCfg: &bwrapConfig{ + Env: os.Environ(), + UID: -1, + GID: -1, + Args: []string{"bash"}, + }, + }, + { + name: "UnshareNet", + args: []string{"--unshare-net", "bash"}, + wantCfg: &bwrapConfig{ + Env: os.Environ(), + UID: -1, + GID: -1, + UnshareNet: true, + Args: []string{"bash"}, + }, + }, + { + name: "ShareNet", + args: []string{"--unshare-net", "--share-net", "bash"}, + wantCfg: &bwrapConfig{ + Env: os.Environ(), + UID: -1, + GID: -1, + UnshareNet: false, + Args: []string{"bash"}, + }, + }, + { + name: "UnshareAll", + args: []string{"--unshare-all", "bash"}, + wantCfg: &bwrapConfig{ + Env: os.Environ(), + UID: -1, + GID: -1, + UnshareUser: true, + UnshareNet: true, + Args: []string{"bash"}, + }, + }, { name: "ValidHostname", args: []string{"--hostname", "test-host", "bash"}, @@ -425,6 +476,43 @@ func TestParseFlags(t *testing.T) { }, }, }, + { + name: "CapDrop", + args: []string{"--cap-drop", "all", "bash"}, + wantCfg: &bwrapConfig{ + Env: os.Environ(), + UID: -1, + GID: -1, + Args: []string{"bash"}, + CapOps: []*CapOp{ + {Type: CapOpDrop, Cap: "all"}, + }, + }, + }, + { + name: "CapAddAndDrop", + args: []string{"--cap-drop", "all", "--cap-add", "net_admin", "bash"}, + wantCfg: &bwrapConfig{ + Env: os.Environ(), + UID: -1, + GID: -1, + Args: []string{"bash"}, + CapOps: []*CapOp{ + {Type: CapOpDrop, Cap: "all"}, + {Type: CapOpAdd, Cap: "net_admin"}, + }, + }, + }, + { + name: "MissingCapAddArg", + args: []string{"--cap-add"}, + errContains: "--cap-add takes 1 argument", + }, + { + name: "MissingCapDropArg", + args: []string{"--cap-drop"}, + errContains: "--cap-drop takes 1 argument", + }, } for _, tc := range tests { diff --git a/runsc/cmd/alias/bwrap/cli.go b/runsc/cmd/alias/bwrap/cli.go index 2d85618fb61..b863a621d20 100644 --- a/runsc/cmd/alias/bwrap/cli.go +++ b/runsc/cmd/alias/bwrap/cli.go @@ -17,6 +17,7 @@ package bwrap import ( "context" "fmt" + "math/rand" "os" "path/filepath" "strconv" @@ -52,6 +53,8 @@ const ( flagUnshareCgroup = "unshare-cgroup" flagUnshareAll = "unshare-all" flagShareNet = "share-net" + flagCapDrop = "cap-drop" + flagCapAdd = "cap-add" ) // Cli implements subcommands.Command for the "bwrap" command. @@ -76,6 +79,8 @@ type Cli struct { unshareAll bool hostname string proc string + capDrop string + capAdd string } // Name implements subcommands.Command.Name. @@ -114,6 +119,8 @@ func (c *Cli) SetFlags(f *flag.FlagSet) { f.StringVar(&c.proc, flagProc, "", "Mount new procfs on DEST") f.BoolVar(&c.unshareCgroup, flagUnshareCgroup, false, "Create new cgroup namespace") f.BoolVar(&c.unshareAll, flagUnshareAll, false, "Unshare every namespace we support by default") + f.StringVar(&c.capDrop, flagCapDrop, "", "Drop capabilities when running as privileged user") + f.StringVar(&c.capAdd, flagCapAdd, "", "Add capabilities when running as privileged user") // Override the default usage function to print the custom usage message. f.Usage = func() { @@ -132,10 +139,16 @@ func (c *Cli) FetchSpec(_ *config.Config, _ *flag.FlagSet) (string, *specs.Spec, // parseBwrapArgs parses the bwrap arguments and returns a bwrapConfig struct. func parseBwrapArgs(bwrapArgs []string) (*bwrapConfig, error) { + // Allocate a random subnet in 192.168.x.2 where x is from 10 to 254 to prevent clashes + // when running multiple instances on the host concurrently. + randSubnet := 10 + rand.Int31n(245) + ip := fmt.Sprintf("192.168.%d.2", randSubnet) + cfg := &bwrapConfig{ Env: os.Environ(), UID: -1, GID: -1, + ip: ip, } var err error for i := 0; i < len(bwrapArgs); { @@ -191,6 +204,10 @@ func parseBwrapArgs(bwrapArgs []string) (*bwrapConfig, error) { i, err = cfg.parseProc(bwrapArgs, i) case flagUnshareAll: i, err = cfg.parseUnshareAll(bwrapArgs, i) + case flagCapDrop: + i, err = cfg.parseCapDrop(bwrapArgs, i) + case flagCapAdd: + i, err = cfg.parseCapAdd(bwrapArgs, i) default: return nil, fmt.Errorf("bwrap: Unknown option: %s", arg) } @@ -382,14 +399,14 @@ func (c *bwrapConfig) parseUserns(args []string, i int) (int, error) { } // parseNoopZeroArg parses flags that are treated as no-ops. -// gVisor's Sentry kernel inherently virtualizes and isolates IPC, PID, and UTS +// gVisor's Sentry kernel inherently virtualizes and isolates IPC, PID, UTS, and Cgroup // namespaces by default. These flags are parsed solely for CLI compatibility func (c *bwrapConfig) parseNoopZeroArg(args []string, i int) (int, error) { return i + 1, nil } func (c *bwrapConfig) parseShareNet(args []string, i int) (int, error) { - c.ShareNet = true + c.UnshareNet = false return i + 1, nil } @@ -399,3 +416,19 @@ func (c *bwrapConfig) parseUnshareAll(args []string, i int) (int, error) { c.UnshareNet = true return i + 1, nil } + +func (c *bwrapConfig) parseCapDrop(args []string, i int) (int, error) { + if i+1 >= len(args) { + return i, fmt.Errorf("--%s takes 1 argument", flagCapDrop) + } + c.CapOps = append(c.CapOps, &CapOp{Type: CapOpDrop, Cap: args[i+1]}) + return i + 2, nil +} + +func (c *bwrapConfig) parseCapAdd(args []string, i int) (int, error) { + if i+1 >= len(args) { + return i, fmt.Errorf("--%s takes 1 argument", flagCapAdd) + } + c.CapOps = append(c.CapOps, &CapOp{Type: CapOpAdd, Cap: args[i+1]}) + return i + 2, nil +}