diff --git a/cmd/nvidia-cdi-hook/update-ldcache/ldconfig_linux.go b/cmd/nvidia-cdi-hook/update-ldcache/ldconfig_linux.go new file mode 100644 index 00000000..c472d14f --- /dev/null +++ b/cmd/nvidia-cdi-hook/update-ldcache/ldconfig_linux.go @@ -0,0 +1,200 @@ +//go:build linux + +/** +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +**/ + +package ldcache + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "syscall" + + securejoin "github.com/cyphar/filepath-securejoin" + + "github.com/moby/sys/reexec" + "github.com/opencontainers/runc/libcontainer/utils" + "golang.org/x/sys/unix" +) + +// pivotRoot will call pivot_root such that rootfs becomes the new root +// filesystem, and everything else is cleaned up. +// This is adapted from the implementation here: +// +// https://github.com/opencontainers/runc/blob/e89a29929c775025419ab0d218a43588b4c12b9a/libcontainer/rootfs_linux.go#L1056-L1113 +// +// With the `mount` and `unmount` calls changed to direct unix.Mount and unix.Unmount calls. +func pivotRoot(rootfs string) error { + // While the documentation may claim otherwise, pivot_root(".", ".") is + // actually valid. What this results in is / being the new root but + // /proc/self/cwd being the old root. Since we can play around with the cwd + // with pivot_root this allows us to pivot without creating directories in + // the rootfs. Shout-outs to the LXC developers for giving us this idea. + + oldroot, err := unix.Open("/", unix.O_DIRECTORY|unix.O_RDONLY, 0) + if err != nil { + return &os.PathError{Op: "open", Path: "/", Err: err} + } + defer unix.Close(oldroot) //nolint: errcheck + + newroot, err := unix.Open(rootfs, unix.O_DIRECTORY|unix.O_RDONLY, 0) + if err != nil { + return &os.PathError{Op: "open", Path: rootfs, Err: err} + } + defer unix.Close(newroot) //nolint: errcheck + + // Change to the new root so that the pivot_root actually acts on it. + if err := unix.Fchdir(newroot); err != nil { + return &os.PathError{Op: "fchdir", Path: "fd " + strconv.Itoa(newroot), Err: err} + } + + if err := unix.PivotRoot(".", "."); err != nil { + return &os.PathError{Op: "pivot_root", Path: ".", Err: err} + } + + // Currently our "." is oldroot (according to the current kernel code). + // However, purely for safety, we will fchdir(oldroot) since there isn't + // really any guarantee from the kernel what /proc/self/cwd will be after a + // pivot_root(2). + + if err := unix.Fchdir(oldroot); err != nil { + return &os.PathError{Op: "fchdir", Path: "fd " + strconv.Itoa(oldroot), Err: err} + } + + // Make oldroot rslave to make sure our unmounts don't propagate to the + // host (and thus bork the machine). We don't use rprivate because this is + // known to cause issues due to races where we still have a reference to a + // mount while a process in the host namespace are trying to operate on + // something they think has no mounts (devicemapper in particular). + if err := unix.Mount("", ".", "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil { + return err + } + // Perform the unmount. MNT_DETACH allows us to unmount /proc/self/cwd. + if err := unix.Unmount(".", unix.MNT_DETACH); err != nil { + return err + } + + // Switch back to our shiny new root. + if err := unix.Chdir("/"); err != nil { + return &os.PathError{Op: "chdir", Path: "/", Err: err} + } + return nil +} + +// mountLdConfig mounts the host ldconfig to the mount namespace of the hook. +// We use WithProcfd to perform the mount operations to ensure that the changes +// are persisted across the pivot root. +func mountLdConfig(hostLdconfigPath string, containerRootDirPath string) (string, error) { + hostLdconfigInfo, err := os.Stat(hostLdconfigPath) + if err != nil { + return "", fmt.Errorf("error reading host ldconfig: %w", err) + } + + hookScratchDirPath := "/var/run/nvidia-ctk-hook" + ldconfigPath := filepath.Join(hookScratchDirPath, "ldconfig") + if err := utils.MkdirAllInRoot(containerRootDirPath, hookScratchDirPath, 0755); err != nil { + return "", fmt.Errorf("error creating hook scratch folder: %w", err) + } + + err = utils.WithProcfd(containerRootDirPath, hookScratchDirPath, func(hookScratchDirFdPath string) error { + return createTmpFs(hookScratchDirFdPath, int(hostLdconfigInfo.Size())) + + }) + if err != nil { + return "", fmt.Errorf("error creating tmpfs: %w", err) + } + + if _, err := createFileInRoot(containerRootDirPath, ldconfigPath, hostLdconfigInfo.Mode()); err != nil { + return "", fmt.Errorf("error creating ldconfig: %w", err) + } + + err = utils.WithProcfd(containerRootDirPath, ldconfigPath, func(ldconfigFdPath string) error { + return unix.Mount(hostLdconfigPath, ldconfigFdPath, "", unix.MS_BIND|unix.MS_RDONLY|unix.MS_NODEV|unix.MS_PRIVATE|unix.MS_NOSYMFOLLOW, "") + }) + if err != nil { + return "", fmt.Errorf("error bind mounting host ldconfig: %w", err) + } + + return ldconfigPath, nil +} + +func createFileInRoot(containerRootDirPath string, destinationPath string, mode os.FileMode) (string, error) { + dest, err := securejoin.SecureJoin(containerRootDirPath, destinationPath) + if err != nil { + return "", err + } + // Make the parent directory. + destDir, destBase := filepath.Split(dest) + destDirFd, err := utils.MkdirAllInRootOpen(containerRootDirPath, destDir, 0755) + if err != nil { + return "", fmt.Errorf("error creating parent dir: %w", err) + } + defer destDirFd.Close() + // Make the target file. We want to avoid opening any file that is + // already there because it could be a "bad" file like an invalid + // device or hung tty that might cause a DoS, so we use mknodat. + // destBase does not contain any "/" components, and mknodat does + // not follow trailing symlinks, so we can safely just call mknodat + // here. + if err := unix.Mknodat(int(destDirFd.Fd()), destBase, unix.S_IFREG|uint32(mode), 0); err != nil { + // If we get EEXIST, there was already an inode there and + // we can consider that a success. + if !errors.Is(err, unix.EEXIST) { + return "", fmt.Errorf("error creating empty file: %w", err) + } + } + return dest, nil +} + +// mountProc mounts a clean proc filesystem in the new root. +func mountProc(newroot string) error { + target := filepath.Join(newroot, "/proc") + + if err := os.MkdirAll(target, 0755); err != nil { + return fmt.Errorf("error creating directory: %w", err) + } + return unix.Mount("proc", target, "proc", 0, "") +} + +// createTmpFs creates a tmpfs at the specified location with the specified size. +func createTmpFs(target string, size int) error { + return unix.Mount("tmpfs", target, "tmpfs", 0, fmt.Sprintf("size=%d", size)) +} + +// createReexecCommand creates a command that can be used to trigger the reexec +// initializer. +// On linux this command runs in new namespaces. +func createReexecCommand(args []string) *exec.Cmd { + cmd := reexec.Command(args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + cmd.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: syscall.CLONE_NEWNS | + syscall.CLONE_NEWUTS | + syscall.CLONE_NEWIPC | + syscall.CLONE_NEWPID | + syscall.CLONE_NEWNET, + } + + return cmd +} diff --git a/cmd/nvidia-cdi-hook/update-ldcache/ldconfig_other.go b/cmd/nvidia-cdi-hook/update-ldcache/ldconfig_other.go new file mode 100644 index 00000000..a6c35261 --- /dev/null +++ b/cmd/nvidia-cdi-hook/update-ldcache/ldconfig_other.go @@ -0,0 +1,51 @@ +//go:build !linux + +/** +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +**/ + +package ldcache + +import ( + "fmt" + "os" + "os/exec" + + "github.com/moby/sys/reexec" +) + +func pivotRoot(newroot string) error { + return fmt.Errorf("not supported") +} + +func mountLdConfig(hostLdconfigPath string, containerRootDirPath string) (string, error) { + return "", fmt.Errorf("not supported") +} + +func mountProc(newroot string) error { + return fmt.Errorf("not supported") +} + +// createReexecCommand creates a command that can be used ot trigger the reexec +// initializer. +func createReexecCommand(args []string) *exec.Cmd { + cmd := reexec.Command(args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + return cmd +} diff --git a/cmd/nvidia-cdi-hook/update-ldcache/safe-exec_linux.go b/cmd/nvidia-cdi-hook/update-ldcache/safe-exec_linux.go index 2e496e37..790ac6a8 100644 --- a/cmd/nvidia-cdi-hook/update-ldcache/safe-exec_linux.go +++ b/cmd/nvidia-cdi-hook/update-ldcache/safe-exec_linux.go @@ -1,3 +1,5 @@ +//go:build linux + /** # Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. # @@ -26,10 +28,9 @@ import ( ) // SafeExec attempts to clone the specified binary (as an memfd, for example) before executing it. -func (m command) SafeExec(path string, args []string, envv []string) error { +func SafeExec(path string, args []string, envv []string) error { safeExe, err := cloneBinary(path) if err != nil { - m.logger.Warningf("Failed to clone binary %q: %v; falling back to Exec", path, err) //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection return syscall.Exec(path, args, envv) } diff --git a/cmd/nvidia-cdi-hook/update-ldcache/safe-exec_other.go b/cmd/nvidia-cdi-hook/update-ldcache/safe-exec_other.go index dff11dd3..71f9ddb5 100644 --- a/cmd/nvidia-cdi-hook/update-ldcache/safe-exec_other.go +++ b/cmd/nvidia-cdi-hook/update-ldcache/safe-exec_other.go @@ -1,5 +1,4 @@ //go:build !linux -// +build !linux /** # Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. @@ -23,7 +22,7 @@ import "syscall" // SafeExec is not implemented on non-linux systems and forwards directly to the // Exec syscall. -func (m *command) SafeExec(path string, args []string, envv []string) error { +func SafeExec(path string, args []string, envv []string) error { //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection return syscall.Exec(path, args, envv) } diff --git a/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go b/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go index 4118ad9c..49b73371 100644 --- a/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go +++ b/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go @@ -19,10 +19,11 @@ package ldcache import ( "errors" "fmt" + "log" "os" - "path/filepath" "strings" + "github.com/moby/sys/reexec" "github.com/urfave/cli/v2" "github.com/NVIDIA/nvidia-container-toolkit/internal/config" @@ -37,6 +38,8 @@ const ( // higher precedence than other libraries on the system, but lower than // the 00-cuda-compat that is included in some containers. ldsoconfdFilenamePattern = "00-nvcr-*.conf" + + reexecUpdateLdCacheCommandName = "reexec-update-ldcache" ) type command struct { @@ -49,6 +52,13 @@ type options struct { containerSpec string } +func init() { + reexec.Register(reexecUpdateLdCacheCommandName, updateLdCacheHandler) + if reexec.Init() { + os.Exit(0) + } +} + // NewCommand constructs an update-ldcache command with the specified logger func NewCommand(logger logger.Interface) *cli.Command { c := command{ @@ -113,11 +123,69 @@ func (m command) run(c *cli.Context, cfg *options) error { return fmt.Errorf("failed to determined container root: %v", err) } - ldconfigPath := m.resolveLDConfigPath(cfg.ldconfigPath) args := []string{ - filepath.Base(ldconfigPath), - // Run ldconfig in the container root directory on the host. - "-r", containerRootDir, + reexecUpdateLdCacheCommandName, + strings.TrimPrefix(config.NormalizeLDConfigPath("@"+cfg.ldconfigPath), "@"), + containerRootDir, + } + args = append(args, cfg.folders.Value()...) + + cmd := createReexecCommand(args) + + return cmd.Run() +} + +// updateLdCacheHandler wraps updateLdCache with error handling. +func updateLdCacheHandler() { + if err := updateLdCache(os.Args); err != nil { + log.Printf("Error updating ldcache: %v", err) + os.Exit(1) + } +} + +// updateLdCache is invoked from a reexec'd handler and provides namespace +// isolation for the operations performed by this hook. +// At the point where this is invoked, we are in a new mount namespace that is +// cloned from the parent. +// +// args[0] is the reexec initializer function name +// args[1] is the path of the ldconfig binary on the host +// args[2] is the container root directory +// The remaining args are folders that need to be added to the ldcache. +func updateLdCache(args []string) error { + if len(args) < 3 { + return fmt.Errorf("incorrect arguments: %v", args) + } + hostLdconfigPath := args[1] + containerRootDirPath := args[2] + + // To prevent leaking the parent proc filesystem, we create a new proc mount + // in the container root. + if err := mountProc(containerRootDirPath); err != nil { + return fmt.Errorf("error mounting /proc: %w", err) + } + + // We mount the host ldconfig before we pivot root since host paths are not + // visible after the pivot root operation. + ldconfigPath, err := mountLdConfig(hostLdconfigPath, containerRootDirPath) + if err != nil { + return fmt.Errorf("error mounting host ldconfig: %w", err) + } + + // We pivot to the container root for the new process, this further limits + // access to the host. + if err := pivotRoot(containerRootDirPath); err != nil { + return fmt.Errorf("error running pivot_root: %w", err) + } + + return runLdconfig(ldconfigPath, args[3:]...) +} + +// runLdconfig runs the ldconfig binary and ensures that the specified directories +// are processed for the ldcache. +func runLdconfig(ldconfigPath string, directories ...string) error { + args := []string{ + "ldconfig", // Explicitly specify using /etc/ld.so.conf since the host's ldconfig may // be configured to use a different config file by default. // Note that since we apply the `-r {{ .containerRootDir }}` argument, /etc/ld.so.conf is @@ -125,48 +193,35 @@ func (m command) run(c *cli.Context, cfg *options) error { "-f", "/etc/ld.so.conf", } - containerRoot := containerRoot(containerRootDir) + containerRoot := containerRoot("/") if containerRoot.hasPath("/etc/ld.so.cache") { args = append(args, "-C", "/etc/ld.so.cache") } else { - m.logger.Debugf("No ld.so.cache found, skipping update") args = append(args, "-N") } - folders := cfg.folders.Value() if containerRoot.hasPath("/etc/ld.so.conf.d") { - err := m.createLdsoconfdFile(containerRoot, ldsoconfdFilenamePattern, folders...) + err := createLdsoconfdFile(ldsoconfdFilenamePattern, directories...) if err != nil { - return fmt.Errorf("failed to update ld.so.conf.d: %v", err) + return fmt.Errorf("failed to update ld.so.conf.d: %w", err) } } else { - args = append(args, folders...) + args = append(args, directories...) } - return m.SafeExec(ldconfigPath, args, nil) + return SafeExec(ldconfigPath, args, nil) } -// resolveLDConfigPath determines the LDConfig path to use for the system. -// On systems such as Ubuntu where `/sbin/ldconfig` is a wrapper around -// /sbin/ldconfig.real, the latter is returned. -func (m command) resolveLDConfigPath(path string) string { - return strings.TrimPrefix(config.NormalizeLDConfigPath("@"+path), "@") -} - -// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/ in the specified root. +// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/. // The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and // contains the specified directories on each line. -func (m command) createLdsoconfdFile(in containerRoot, pattern string, dirs ...string) error { +func createLdsoconfdFile(pattern string, dirs ...string) error { if len(dirs) == 0 { - m.logger.Debugf("No directories to add to /etc/ld.so.conf") return nil } - ldsoconfdDir, err := in.resolve("/etc/ld.so.conf.d") - if err != nil { - return err - } + ldsoconfdDir := "/etc/ld.so.conf.d" if err := os.MkdirAll(ldsoconfdDir, 0755); err != nil { return fmt.Errorf("failed to create ld.so.conf.d: %w", err) } @@ -175,9 +230,9 @@ func (m command) createLdsoconfdFile(in containerRoot, pattern string, dirs ...s if err != nil { return fmt.Errorf("failed to create config file: %w", err) } - defer configFile.Close() - - m.logger.Debugf("Adding directories %v to %v", dirs, configFile.Name()) + defer func() { + _ = configFile.Close() + }() added := make(map[string]bool) for _, dir := range dirs { diff --git a/go.mod b/go.mod index 7d9e1f8b..c941d6d1 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.23.0 require ( github.com/NVIDIA/go-nvlib v0.7.2 github.com/NVIDIA/go-nvml v0.12.4-1 + github.com/cyphar/filepath-securejoin v0.4.1 + github.com/moby/sys/reexec v0.1.0 github.com/moby/sys/symlink v0.3.0 github.com/opencontainers/runc v1.3.0 github.com/opencontainers/runtime-spec v1.2.1 @@ -20,7 +22,6 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/google/uuid v1.6.0 // indirect diff --git a/go.sum b/go.sum index 384f1095..9a1d39e3 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs= +github.com/moby/sys/reexec v0.1.0 h1:RrBi8e0EBTLEgfruBOFcxtElzRGTEUkeIFaVXgU7wok= +github.com/moby/sys/reexec v0.1.0/go.mod h1:EqjBg8F3X7iZe5pU6nRZnYCMUTXoxsjiIfHup5wYIN8= github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU= github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= diff --git a/vendor/github.com/moby/sys/reexec/LICENSE b/vendor/github.com/moby/sys/reexec/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/vendor/github.com/moby/sys/reexec/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/moby/sys/reexec/reexec.go b/vendor/github.com/moby/sys/reexec/reexec.go new file mode 100644 index 00000000..c3a0c925 --- /dev/null +++ b/vendor/github.com/moby/sys/reexec/reexec.go @@ -0,0 +1,83 @@ +// Package reexec facilitates the busybox style reexec of a binary. +// +// Handlers can be registered with a name and the argv 0 of the exec of +// the binary will be used to find and execute custom init paths. +// +// It is used to work around forking limitations when using Go. +package reexec + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" +) + +var registeredInitializers = make(map[string]func()) + +// Register adds an initialization func under the specified name. It panics +// if the given name is already registered. +func Register(name string, initializer func()) { + if _, exists := registeredInitializers[name]; exists { + panic(fmt.Sprintf("reexec func already registered under name %q", name)) + } + + registeredInitializers[name] = initializer +} + +// Init is called as the first part of the exec process and returns true if an +// initialization function was called. +func Init() bool { + if initializer, ok := registeredInitializers[os.Args[0]]; ok { + initializer() + return true + } + return false +} + +// Command returns an [*exec.Cmd] with its Path set to the path of the current +// binary using the result of [Self]. +// +// On Linux, the Pdeathsig of [*exec.Cmd.SysProcAttr] is set to SIGTERM. +// This signal is sent to the process when the OS thread that created +// the process dies. +// +// It is the caller's responsibility to ensure that the creating thread is +// not terminated prematurely. See https://go.dev/issue/27505 for more details. +func Command(args ...string) *exec.Cmd { + return command(args...) +} + +// Self returns the path to the current process's binary. +// +// On Linux, it returns "/proc/self/exe", which provides the in-memory version +// of the current binary. This makes it safe to delete or replace the on-disk +// binary (os.Args[0]). +// +// On Other platforms, it attempts to look up the absolute path for os.Args[0], +// or otherwise returns os.Args[0] as-is. For example if current binary is +// "my-binary" at "/usr/bin/" (or "my-binary.exe" at "C:\" on Windows), +// then it returns "/usr/bin/my-binary" and "C:\my-binary.exe" respectively. +func Self() string { + if runtime.GOOS == "linux" { + return "/proc/self/exe" + } + return naiveSelf() +} + +func naiveSelf() string { + name := os.Args[0] + if filepath.Base(name) == name { + if lp, err := exec.LookPath(name); err == nil { + return lp + } + } + // handle conversion of relative paths to absolute + if absName, err := filepath.Abs(name); err == nil { + return absName + } + // if we couldn't get absolute name, return original + // (NOTE: Go only errors on Abs() if os.Getwd fails) + return name +} diff --git a/vendor/github.com/moby/sys/reexec/reexec_linux.go b/vendor/github.com/moby/sys/reexec/reexec_linux.go new file mode 100644 index 00000000..03f600e0 --- /dev/null +++ b/vendor/github.com/moby/sys/reexec/reexec_linux.go @@ -0,0 +1,16 @@ +package reexec + +import ( + "os/exec" + "syscall" +) + +func command(args ...string) *exec.Cmd { + return &exec.Cmd{ + Path: Self(), + Args: args, + SysProcAttr: &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, + }, + } +} diff --git a/vendor/github.com/moby/sys/reexec/reexec_other.go b/vendor/github.com/moby/sys/reexec/reexec_other.go new file mode 100644 index 00000000..498d28bc --- /dev/null +++ b/vendor/github.com/moby/sys/reexec/reexec_other.go @@ -0,0 +1,14 @@ +//go:build !linux + +package reexec + +import ( + "os/exec" +) + +func command(args ...string) *exec.Cmd { + return &exec.Cmd{ + Path: Self(), + Args: args, + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 87ed5a01..9df5d580 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -31,6 +31,9 @@ github.com/google/uuid ## explicit # github.com/kr/pretty v0.3.1 ## explicit; go 1.12 +# github.com/moby/sys/reexec v0.1.0 +## explicit; go 1.18 +github.com/moby/sys/reexec # github.com/moby/sys/symlink v0.3.0 ## explicit; go 1.17 github.com/moby/sys/symlink