Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package runc for openSUSE:Factory checked in at 2024-01-04 15:56:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/runc (Old) and /work/SRC/openSUSE:Factory/.runc.new.28375 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "runc" Thu Jan 4 15:56:37 2024 rev:59 rq:1136047 version:1.1.11 Changes: -------- --- /work/SRC/openSUSE:Factory/runc/runc.changes 2023-11-07 21:25:43.438111684 +0100 +++ /work/SRC/openSUSE:Factory/.runc.new.28375/runc.changes 2024-01-04 15:57:48.769429235 +0100 @@ -1,0 +2,6 @@ +Tue Jan 2 03:02:16 UTC 2024 - Aleksa Sarai <[email protected]> + +- Update to runc v1.1.11. Upstream changelog is available from + <https://github.com/opencontainers/runc/releases/tag/v1.1.11>. + +------------------------------------------------------------------- Old: ---- runc-1.1.10.tar.xz runc-1.1.10.tar.xz.asc New: ---- runc-1.1.11.tar.xz runc-1.1.11.tar.xz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ runc.spec ++++++ --- /var/tmp/diff_new_pack.LYcUy1/_old 2024-01-04 15:57:49.505456122 +0100 +++ /var/tmp/diff_new_pack.LYcUy1/_new 2024-01-04 15:57:49.505456122 +0100 @@ -1,7 +1,7 @@ # # spec file for package runc # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,13 +18,13 @@ # MANUAL: Make sure you update this each time you update runc. -%define git_version 18a0cb0f32bcac2ecc9a10f327d282759c144dab -%define git_short 18a0cb0f32bc +%define git_version 4bccb38cc9cf198d52bebf2b3a90cd14e7af8c06 +%define git_short 4bccb38cc9cf %define project github.com/opencontainers/runc Name: runc -Version: 1.1.10 +Version: 1.1.11 Release: 0 Summary: Tool for spawning and running OCI containers License: Apache-2.0 ++++++ runc-1.1.10.tar.xz -> runc-1.1.11.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/CHANGELOG.md new/runc-1.1.11/CHANGELOG.md --- old/runc-1.1.10/CHANGELOG.md 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/CHANGELOG.md 2024-01-02 03:34:16.000000000 +0100 @@ -6,6 +6,23 @@ ## [Unreleased 1.1.z] +## [1.1.11] - 2024-01-01 + +> Happy New Year! + +### Fixed + +* Fix several issues with userns path handling. (#4122, #4124, #4134, #4144) + +### Changed + + * Support memory.peak and memory.swap.peak in cgroups v2. + Add `swapOnlyUsage` in `MemoryStats`. This field reports swap-only usage. + For cgroupv1, `Usage` and `Failcnt` are set by subtracting memory usage + from memory+swap usage. For cgroupv2, `Usage`, `Limit`, and `MaxUsage` + are set. (#4000, #4010, #4131) + * build(deps): bump github.com/cyphar/filepath-securejoin. (#4140) + ## [1.1.10] - 2023-10-31 > Åruba, przykrÄcona we Ånie, nie zmieni sytuacji, jaka panuje na jawie. @@ -476,7 +493,8 @@ [1.0.1]: https://github.com/opencontainers/runc/compare/v1.0.0...v1.0.1 <!-- 1.1.z patch releases --> -[Unreleased 1.1.z]: https://github.com/opencontainers/runc/compare/v1.1.10...release-1.1 +[Unreleased 1.1.z]: https://github.com/opencontainers/runc/compare/v1.1.11...release-1.1 +[1.1.11]: https://github.com/opencontainers/runc/compare/v1.1.10...v1.1.11 [1.1.10]: https://github.com/opencontainers/runc/compare/v1.1.9...v1.1.10 [1.1.9]: https://github.com/opencontainers/runc/compare/v1.1.8...v1.1.9 [1.1.8]: https://github.com/opencontainers/runc/compare/v1.1.7...v1.1.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/VERSION new/runc-1.1.11/VERSION --- old/runc-1.1.10/VERSION 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/VERSION 2024-01-02 03:34:16.000000000 +0100 @@ -1 +1 @@ -1.1.10 +1.1.11 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/go.mod new/runc-1.1.11/go.mod --- old/runc-1.1.10/go.mod 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/go.mod 2024-01-02 03:34:16.000000000 +0100 @@ -7,7 +7,7 @@ github.com/cilium/ebpf v0.7.0 github.com/containerd/console v1.0.3 github.com/coreos/go-systemd/v22 v22.3.2 - github.com/cyphar/filepath-securejoin v0.2.3 + github.com/cyphar/filepath-securejoin v0.2.4 github.com/docker/go-units v0.4.0 github.com/godbus/dbus/v5 v5.0.6 github.com/moby/sys/mountinfo v0.5.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/go.sum new/runc-1.1.11/go.sum --- old/runc-1.1.10/go.sum 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/go.sum 2024-01-02 03:34:16.000000000 +0100 @@ -9,8 +9,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/cgroups/fs/memory.go new/runc-1.1.11/libcontainer/cgroups/fs/memory.go --- old/runc-1.1.10/libcontainer/cgroups/fs/memory.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/cgroups/fs/memory.go 2024-01-02 03:34:16.000000000 +0100 @@ -170,6 +170,10 @@ return err } stats.MemoryStats.SwapUsage = swapUsage + stats.MemoryStats.SwapOnlyUsage = cgroups.MemoryData{ + Usage: swapUsage.Usage - memoryUsage.Usage, + Failcnt: swapUsage.Failcnt - memoryUsage.Failcnt, + } kernelUsage, err := getMemoryData(path, "kmem") if err != nil { return err diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/cgroups/fs/memory_test.go new/runc-1.1.11/libcontainer/cgroups/fs/memory_test.go --- old/runc-1.1.10/libcontainer/cgroups/fs/memory_test.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/cgroups/fs/memory_test.go 2024-01-02 03:34:16.000000000 +0100 @@ -249,12 +249,13 @@ t.Fatal(err) } expectedStats := cgroups.MemoryStats{ - Cache: 512, - Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, - SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, - KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, - Stats: map[string]uint64{"cache": 512, "rss": 1024}, - UseHierarchy: true, + Cache: 512, + Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, + SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, + SwapOnlyUsage: cgroups.MemoryData{Usage: 0, MaxUsage: 0, Failcnt: 0, Limit: 0}, + KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, + Stats: map[string]uint64{"cache": 512, "rss": 1024}, + UseHierarchy: true, PageUsageByNUMA: cgroups.PageUsageByNUMA{ PageUsageByNUMAInner: cgroups.PageUsageByNUMAInner{ Total: cgroups.PageStats{Total: 44611, Nodes: map[uint8]uint64{0: 32631, 1: 7501, 2: 1982, 3: 2497}}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/cgroups/fs2/memory.go new/runc-1.1.11/libcontainer/cgroups/fs2/memory.go --- old/runc-1.1.10/libcontainer/cgroups/fs2/memory.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/cgroups/fs2/memory.go 2024-01-02 03:34:16.000000000 +0100 @@ -100,7 +100,7 @@ memoryUsage, err := getMemoryDataV2(dirPath, "") if err != nil { if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint { - // The root cgroup does not have memory.{current,max} + // The root cgroup does not have memory.{current,max,peak} // so emulate those using data from /proc/meminfo and // /sys/fs/cgroup/memory.stat return rootStatsFromMeminfo(stats) @@ -108,10 +108,12 @@ return err } stats.MemoryStats.Usage = memoryUsage - swapUsage, err := getMemoryDataV2(dirPath, "swap") + swapOnlyUsage, err := getMemoryDataV2(dirPath, "swap") if err != nil { return err } + stats.MemoryStats.SwapOnlyUsage = swapOnlyUsage + swapUsage := swapOnlyUsage // As cgroup v1 reports SwapUsage values as mem+swap combined, // while in cgroup v2 swap values do not include memory, // report combined mem+swap for v1 compatibility. @@ -119,6 +121,9 @@ if swapUsage.Limit != math.MaxUint64 { swapUsage.Limit += memoryUsage.Limit } + // The `MaxUsage` of mem+swap cannot simply combine mem with + // swap. So set it to 0 for v1 compatibility. + swapUsage.MaxUsage = 0 stats.MemoryStats.SwapUsage = swapUsage return nil @@ -133,6 +138,7 @@ } usage := moduleName + ".current" limit := moduleName + ".max" + maxUsage := moduleName + ".peak" value, err := fscommon.GetCgroupParamUint(path, usage) if err != nil { @@ -152,6 +158,14 @@ } memoryData.Limit = value + // `memory.peak` since kernel 5.19 + // `memory.swap.peak` since kernel 6.5 + value, err = fscommon.GetCgroupParamUint(path, maxUsage) + if err != nil && !os.IsNotExist(err) { + return cgroups.MemoryData{}, err + } + memoryData.MaxUsage = value + return memoryData, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/cgroups/fs2/memory_test.go new/runc-1.1.11/libcontainer/cgroups/fs2/memory_test.go --- old/runc-1.1.10/libcontainer/cgroups/fs2/memory_test.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/cgroups/fs2/memory_test.go 2024-01-02 03:34:16.000000000 +0100 @@ -94,6 +94,10 @@ t.Fatal(err) } + if err := os.WriteFile(filepath.Join(fakeCgroupDir, "memory.peak"), []byte("987654321"), 0o644); err != nil { + t.Fatal(err) + } + gotStats := cgroups.NewStats() // use a fake root path to trigger the pod cgroup lookup. @@ -107,6 +111,18 @@ if gotStats.MemoryStats.Usage.Usage != expectedUsageBytes { t.Errorf("parsed cgroupv2 memory.stat doesn't match expected result: \ngot %#v\nexpected %#v\n", gotStats.MemoryStats.Usage.Usage, expectedUsageBytes) } + + // result should be "memory.max" + var expectedLimitBytes uint64 = 999999999 + if gotStats.MemoryStats.Usage.Limit != expectedLimitBytes { + t.Errorf("parsed cgroupv2 memory.stat doesn't match expected result: \ngot %#v\nexpected %#v\n", gotStats.MemoryStats.Usage.Limit, expectedLimitBytes) + } + + // result should be "memory.peak" + var expectedMaxUsageBytes uint64 = 987654321 + if gotStats.MemoryStats.Usage.MaxUsage != expectedMaxUsageBytes { + t.Errorf("parsed cgroupv2 memory.stat doesn't match expected result: \ngot %#v\nexpected %#v\n", gotStats.MemoryStats.Usage.MaxUsage, expectedMaxUsageBytes) + } } func TestRootStatsFromMeminfo(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/cgroups/stats.go new/runc-1.1.11/libcontainer/cgroups/stats.go --- old/runc-1.1.10/libcontainer/cgroups/stats.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/cgroups/stats.go 2024-01-02 03:34:16.000000000 +0100 @@ -78,6 +78,8 @@ Usage MemoryData `json:"usage,omitempty"` // usage of memory + swap SwapUsage MemoryData `json:"swap_usage,omitempty"` + // usage of swap only + SwapOnlyUsage MemoryData `json:"swap_only_usage,omitempty"` // usage of kernel memory KernelUsage MemoryData `json:"kernel_usage,omitempty"` // usage of kernel TCP memory diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/configs/config.go new/runc-1.1.11/libcontainer/configs/config.go --- old/runc-1.1.10/libcontainer/configs/config.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/configs/config.go 2024-01-02 03:34:16.000000000 +0100 @@ -21,9 +21,9 @@ // IDMap represents UID/GID Mappings for User Namespaces. type IDMap struct { - ContainerID int `json:"container_id"` - HostID int `json:"host_id"` - Size int `json:"size"` + ContainerID int64 `json:"container_id"` + HostID int64 `json:"host_id"` + Size int64 `json:"size"` } // Seccomp represents syscall restrictions diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/configs/config_linux.go new/runc-1.1.11/libcontainer/configs/config_linux.go --- old/runc-1.1.10/libcontainer/configs/config_linux.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/configs/config_linux.go 2024-01-02 03:34:16.000000000 +0100 @@ -1,6 +1,10 @@ package configs -import "errors" +import ( + "errors" + "fmt" + "math" +) var ( errNoUIDMap = errors.New("User namespaces enabled, but no uid mappings found.") @@ -16,11 +20,18 @@ if c.UidMappings == nil { return -1, errNoUIDMap } - id, found := c.hostIDFromMapping(containerId, c.UidMappings) + id, found := c.hostIDFromMapping(int64(containerId), c.UidMappings) if !found { return -1, errNoUserMap } - return id, nil + // If we are a 32-bit binary running on a 64-bit system, it's possible + // the mapped user is too large to store in an int, which means we + // cannot do the mapping. We can't just return an int64, because + // os.Setuid() takes an int. + if id > math.MaxInt { + return -1, fmt.Errorf("mapping for uid %d (host id %d) is larger than native integer size (%d)", containerId, id, math.MaxInt) + } + return int(id), nil } // Return unchanged id. return containerId, nil @@ -39,11 +50,18 @@ if c.GidMappings == nil { return -1, errNoGIDMap } - id, found := c.hostIDFromMapping(containerId, c.GidMappings) + id, found := c.hostIDFromMapping(int64(containerId), c.GidMappings) if !found { return -1, errNoGroupMap } - return id, nil + // If we are a 32-bit binary running on a 64-bit system, it's possible + // the mapped user is too large to store in an int, which means we + // cannot do the mapping. We can't just return an int64, because + // os.Setgid() takes an int. + if id > math.MaxInt { + return -1, fmt.Errorf("mapping for gid %d (host id %d) is larger than native integer size (%d)", containerId, id, math.MaxInt) + } + return int(id), nil } // Return unchanged id. return containerId, nil @@ -57,7 +75,7 @@ // Utility function that gets a host ID for a container ID from user namespace map // if that ID is present in the map. -func (c Config) hostIDFromMapping(containerID int, uMap []IDMap) (int, bool) { +func (c Config) hostIDFromMapping(containerID int64, uMap []IDMap) (int64, bool) { for _, m := range uMap { if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) { hostID := m.HostID + (containerID - m.ContainerID) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/configs/validate/rootless.go new/runc-1.1.11/libcontainer/configs/validate/rootless.go --- old/runc-1.1.10/libcontainer/configs/validate/rootless.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/configs/validate/rootless.go 2024-01-02 03:34:16.000000000 +0100 @@ -28,25 +28,18 @@ return nil } -func hasIDMapping(id int, mappings []configs.IDMap) bool { - for _, m := range mappings { - if id >= m.ContainerID && id < m.ContainerID+m.Size { - return true - } - } - return false -} - func rootlessEUIDMappings(config *configs.Config) error { if !config.Namespaces.Contains(configs.NEWUSER) { return errors.New("rootless container requires user namespaces") } - - if len(config.UidMappings) == 0 { - return errors.New("rootless containers requires at least one UID mapping") - } - if len(config.GidMappings) == 0 { - return errors.New("rootless containers requires at least one GID mapping") + // We only require mappings if we are not joining another userns. + if path := config.Namespaces.PathOf(configs.NEWUSER); path == "" { + if len(config.UidMappings) == 0 { + return errors.New("rootless containers requires at least one UID mapping") + } + if len(config.GidMappings) == 0 { + return errors.New("rootless containers requires at least one GID mapping") + } } return nil } @@ -70,8 +63,8 @@ // Ignore unknown mount options. continue } - if !hasIDMapping(uid, config.UidMappings) { - return errors.New("cannot specify uid= mount options for unmapped uid in rootless containers") + if _, err := config.HostUID(uid); err != nil { + return fmt.Errorf("cannot specify uid=%d mount option for rootless container: %w", uid, err) } } @@ -82,8 +75,8 @@ // Ignore unknown mount options. continue } - if !hasIDMapping(gid, config.GidMappings) { - return errors.New("cannot specify gid= mount options for unmapped gid in rootless containers") + if _, err := config.HostGID(gid); err != nil { + return fmt.Errorf("cannot specify gid=%d mount option for rootless container: %w", gid, err) } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/configs/validate/validator.go new/runc-1.1.11/libcontainer/configs/validate/validator.go --- old/runc-1.1.10/libcontainer/configs/validate/validator.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/configs/validate/validator.go 2024-01-02 03:34:16.000000000 +0100 @@ -109,11 +109,19 @@ func (v *ConfigValidator) usernamespace(config *configs.Config) error { if config.Namespaces.Contains(configs.NEWUSER) { if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { - return errors.New("USER namespaces aren't enabled in the kernel") + return errors.New("user namespaces aren't enabled in the kernel") } + hasPath := config.Namespaces.PathOf(configs.NEWUSER) != "" + hasMappings := config.UidMappings != nil || config.GidMappings != nil + if !hasPath && !hasMappings { + return errors.New("user namespaces enabled, but no namespace path to join nor mappings to apply specified") + } + // The hasPath && hasMappings validation case is handled in specconv -- + // we cache the mappings in Config during specconv in the hasPath case, + // so we cannot do that validation here. } else { if config.UidMappings != nil || config.GidMappings != nil { - return errors.New("User namespace mappings specified, but USER namespace isn't enabled in the config") + return errors.New("user namespace mappings specified, but user namespace isn't enabled in the config") } } return nil diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/configs/validate/validator_test.go new/runc-1.1.11/libcontainer/configs/validate/validator_test.go --- old/runc-1.1.10/libcontainer/configs/validate/validator_test.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/configs/validate/validator_test.go 2024-01-02 03:34:16.000000000 +0100 @@ -150,7 +150,7 @@ } } -func TestValidateUsernamespace(t *testing.T) { +func TestValidateUserNamespace(t *testing.T) { if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { t.Skip("Test requires userns.") } @@ -161,6 +161,8 @@ {Type: configs.NEWUSER}, }, ), + UidMappings: []configs.IDMap{{HostID: 0, ContainerID: 123, Size: 100}}, + GidMappings: []configs.IDMap{{HostID: 0, ContainerID: 123, Size: 100}}, } validator := New() @@ -170,11 +172,11 @@ } } -func TestValidateUsernamespaceWithoutUserNS(t *testing.T) { - uidMap := configs.IDMap{ContainerID: 123} +func TestValidateUsernsMappingWithoutNamespace(t *testing.T) { config := &configs.Config{ Rootfs: "/var", - UidMappings: []configs.IDMap{uidMap}, + UidMappings: []configs.IDMap{{HostID: 0, ContainerID: 123, Size: 100}}, + GidMappings: []configs.IDMap{{HostID: 0, ContainerID: 123, Size: 100}}, } validator := New() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/container_linux.go new/runc-1.1.11/libcontainer/container_linux.go --- old/runc-1.1.10/libcontainer/container_linux.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/container_linux.go 2024-01-02 03:34:16.000000000 +0100 @@ -2268,7 +2268,7 @@ func requiresRootOrMappingTool(c *configs.Config) bool { gidMap := []configs.IDMap{ - {ContainerID: 0, HostID: os.Getegid(), Size: 1}, + {ContainerID: 0, HostID: int64(os.Getegid()), Size: 1}, } return !reflect.DeepEqual(c.GidMappings, gidMap) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/integration/exec_test.go new/runc-1.1.11/libcontainer/integration/exec_test.go --- old/runc-1.1.10/libcontainer/integration/exec_test.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/integration/exec_test.go 2024-01-02 03:34:16.000000000 +0100 @@ -18,6 +18,7 @@ "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/cgroups/systemd" "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/userns" "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" @@ -1588,6 +1589,11 @@ config2 := newTemplateConfig(t, &tParam{userns: true}) config2.Namespaces.Add(configs.NEWNET, netns1) config2.Namespaces.Add(configs.NEWUSER, userns1) + // Emulate specconv.setupUserNamespace(). + uidMap, gidMap, err := userns.GetUserNamespaceMappings(userns1) + ok(t, err) + config2.UidMappings = uidMap + config2.GidMappings = gidMap config2.Cgroups.Path = "integration/test2" container2, err := newContainer(t, config2) ok(t, err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/specconv/spec_linux.go new/runc-1.1.11/libcontainer/specconv/spec_linux.go --- old/runc-1.1.10/libcontainer/specconv/spec_linux.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/specconv/spec_linux.go 2024-01-02 03:34:16.000000000 +0100 @@ -18,6 +18,7 @@ "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/seccomp" + "github.com/opencontainers/runc/libcontainer/userns" libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils" "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" @@ -926,9 +927,9 @@ func setupUserNamespace(spec *specs.Spec, config *configs.Config) error { create := func(m specs.LinuxIDMapping) configs.IDMap { return configs.IDMap{ - HostID: int(m.HostID), - ContainerID: int(m.ContainerID), - Size: int(m.Size), + HostID: int64(m.HostID), + ContainerID: int64(m.ContainerID), + Size: int64(m.Size), } } if spec.Linux != nil { @@ -939,6 +940,40 @@ config.GidMappings = append(config.GidMappings, create(m)) } } + if path := config.Namespaces.PathOf(configs.NEWUSER); path != "" { + // Cache the current userns mappings in our configuration, so that we + // can calculate uid and gid mappings within runc. These mappings are + // never used for configuring the container if the path is set. + uidMap, gidMap, err := userns.GetUserNamespaceMappings(path) + if err != nil { + return fmt.Errorf("failed to cache mappings for userns: %w", err) + } + // We cannot allow uid or gid mappings to be set if we are also asked + // to join a userns. + if config.UidMappings != nil || config.GidMappings != nil { + // FIXME: It turns out that containerd and CRIO pass both a userns + // path and the mappings of the namespace in the same config.json. + // Such a configuration is technically not valid, but we used to + // require mappings be specified, and thus users worked around our + // bug -- so we can't regress it at the moment. But we also don't + // want to produce broken behaviour if the mapping doesn't match + // the userns. So (for now) we output a warning if the actual + // userns mappings match the configuration, otherwise we return an + // error. + if !userns.IsSameMapping(uidMap, config.UidMappings) || + !userns.IsSameMapping(gidMap, config.GidMappings) { + return errors.New("user namespaces enabled, but both namespace path and non-matching mapping specified -- you may only provide one") + } + logrus.Warnf("config.json has both a userns path to join and a matching userns mapping specified -- you may only provide one. Future versions of runc may return an error with this configuration, please report a bug on <https://github.com/opencontainers/runc> if you see this warning and cannot update your configuration.") + } + + config.UidMappings = uidMap + config.GidMappings = gidMap + logrus.WithFields(logrus.Fields{ + "uid_map": uidMap, + "gid_map": gidMap, + }).Debugf("config uses path-based userns configuration -- current uid and gid mappings cached") + } rootUID, err := config.HostRootUID() if err != nil { return err diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/specconv/spec_linux_test.go new/runc-1.1.11/libcontainer/specconv/spec_linux_test.go --- old/runc-1.1.10/libcontainer/specconv/spec_linux_test.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/libcontainer/specconv/spec_linux_test.go 2024-01-02 03:34:16.000000000 +0100 @@ -610,6 +610,40 @@ } } +func TestUserNamespaceMappingAndPath(t *testing.T) { + if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { + t.Skip("Test requires userns.") + } + + spec := &specs.Spec{ + Root: &specs.Root{ + Path: "rootfs", + }, + Linux: &specs.Linux{ + UIDMappings: []specs.LinuxIDMapping{ + {ContainerID: 0, HostID: 1000, Size: 1000}, + }, + GIDMappings: []specs.LinuxIDMapping{ + {ContainerID: 0, HostID: 2000, Size: 1000}, + }, + Namespaces: []specs.LinuxNamespace{ + { + Type: "user", + Path: "/proc/1/ns/user", + }, + }, + }, + } + + _, err := CreateLibcontainerConfig(&CreateOpts{ + Spec: spec, + }) + + if !strings.Contains(err.Error(), "both namespace path and non-matching mapping specified") { + t.Errorf("user namespace with path and non-matching mapping should be forbidden, got error %v", err) + } +} + func TestNonZeroEUIDCompatibleSpecconvValidate(t *testing.T) { if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { t.Skip("Test requires userns.") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/userns/userns_maps.c new/runc-1.1.11/libcontainer/userns/userns_maps.c --- old/runc-1.1.10/libcontainer/userns/userns_maps.c 1970-01-01 01:00:00.000000000 +0100 +++ new/runc-1.1.11/libcontainer/userns/userns_maps.c 2024-01-02 03:34:16.000000000 +0100 @@ -0,0 +1,79 @@ +#define _GNU_SOURCE +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <unistd.h> +#include <stdarg.h> +#include <stdlib.h> + +/* + * All of the code here is run inside an aync-signal-safe context, so we need + * to be careful to not call any functions that could cause issues. In theory, + * since we are a Go program, there are fewer restrictions in practice, it's + * better to be safe than sorry. + * + * The only exception is exit, which we need to call to make sure we don't + * return into runc. + */ + +void bail(int pipefd, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vdprintf(pipefd, fmt, args); + va_end(args); + + exit(1); +} + +int spawn_userns_cat(char *userns_path, char *path, int outfd, int errfd) +{ + char buffer[4096] = { 0 }; + + pid_t child = fork(); + if (child != 0) + return child; + /* in child */ + + /* Join the target userns. */ + int nsfd = open(userns_path, O_RDONLY); + if (nsfd < 0) + bail(errfd, "open userns path %s failed: %m", userns_path); + + int err = setns(nsfd, CLONE_NEWUSER); + if (err < 0) + bail(errfd, "setns %s failed: %m", userns_path); + + close(nsfd); + + /* Pipe the requested file contents. */ + int fd = open(path, O_RDONLY); + if (fd < 0) + bail(errfd, "open %s in userns %s failed: %m", path, userns_path); + + int nread, ntotal = 0; + while ((nread = read(fd, buffer, sizeof(buffer))) != 0) { + if (nread < 0) + bail(errfd, "read bytes from %s failed (after %d total bytes read): %m", path, ntotal); + ntotal += nread; + + int nwritten = 0; + while (nwritten < nread) { + int n = write(outfd, buffer, nread - nwritten); + if (n < 0) + bail(errfd, "write %d bytes from %s failed (after %d bytes written): %m", + nread - nwritten, path, nwritten); + nwritten += n; + } + if (nread != nwritten) + bail(errfd, "mismatch for bytes read and written: %d read != %d written", nread, nwritten); + } + + close(fd); + close(outfd); + close(errfd); + + /* We must exit here, otherwise we would return into a forked runc. */ + exit(0); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/libcontainer/userns/userns_maps_linux.go new/runc-1.1.11/libcontainer/userns/userns_maps_linux.go --- old/runc-1.1.10/libcontainer/userns/userns_maps_linux.go 1970-01-01 01:00:00.000000000 +0100 +++ new/runc-1.1.11/libcontainer/userns/userns_maps_linux.go 2024-01-02 03:34:16.000000000 +0100 @@ -0,0 +1,186 @@ +//go:build linux + +package userns + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os" + "unsafe" + + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/sirupsen/logrus" +) + +/* +#include <stdlib.h> +extern int spawn_userns_cat(char *userns_path, char *path, int outfd, int errfd); +*/ +import "C" + +func parseIdmapData(data []byte) (ms []configs.IDMap, err error) { + scanner := bufio.NewScanner(bytes.NewReader(data)) + for scanner.Scan() { + var m configs.IDMap + line := scanner.Text() + if _, err := fmt.Sscanf(line, "%d %d %d", &m.ContainerID, &m.HostID, &m.Size); err != nil { + return nil, fmt.Errorf("parsing id map failed: invalid format in line %q: %w", line, err) + } + ms = append(ms, m) + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("parsing id map failed: %w", err) + } + return ms, nil +} + +// Do something equivalent to nsenter --user=<nsPath> cat <path>, but more +// efficiently. Returns the contents of the requested file from within the user +// namespace. +func spawnUserNamespaceCat(nsPath string, path string) ([]byte, error) { + rdr, wtr, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("create pipe for userns spawn failed: %w", err) + } + defer rdr.Close() + defer wtr.Close() + + errRdr, errWtr, err := os.Pipe() + if err != nil { + return nil, fmt.Errorf("create error pipe for userns spawn failed: %w", err) + } + defer errRdr.Close() + defer errWtr.Close() + + cNsPath := C.CString(nsPath) + defer C.free(unsafe.Pointer(cNsPath)) + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + childPid := C.spawn_userns_cat(cNsPath, cPath, C.int(wtr.Fd()), C.int(errWtr.Fd())) + + if childPid < 0 { + return nil, fmt.Errorf("failed to spawn fork for userns") + } else if childPid == 0 { + // this should never happen + panic("runc executing inside fork child -- unsafe state!") + } + + // We are in the parent -- close the write end of the pipe before reading. + wtr.Close() + output, err := io.ReadAll(rdr) + rdr.Close() + if err != nil { + return nil, fmt.Errorf("reading from userns spawn failed: %w", err) + } + + // Ditto for the error pipe. + errWtr.Close() + errOutput, err := io.ReadAll(errRdr) + errRdr.Close() + if err != nil { + return nil, fmt.Errorf("reading from userns spawn error pipe failed: %w", err) + } + errOutput = bytes.TrimSpace(errOutput) + + // Clean up the child. + child, err := os.FindProcess(int(childPid)) + if err != nil { + return nil, fmt.Errorf("could not find userns spawn process: %w", err) + } + state, err := child.Wait() + if err != nil { + return nil, fmt.Errorf("failed to wait for userns spawn process: %w", err) + } + if !state.Success() { + errStr := string(errOutput) + if errStr == "" { + errStr = fmt.Sprintf("unknown error (status code %d)", state.ExitCode()) + } + return nil, fmt.Errorf("userns spawn: %s", errStr) + } else if len(errOutput) > 0 { + // We can just ignore weird output in the error pipe if the process + // didn't bail(), but for completeness output for debugging. + logrus.Debugf("userns spawn succeeded but unexpected error message found: %s", string(errOutput)) + } + // The subprocess succeeded, return whatever it wrote to the pipe. + return output, nil +} + +func GetUserNamespaceMappings(nsPath string) (uidMap, gidMap []configs.IDMap, err error) { + var ( + pid int + extra rune + tryFastPath bool + ) + + // nsPath is usually of the form /proc/<pid>/ns/user, which means that we + // already have a pid that is part of the user namespace and thus we can + // just use the pid to read from /proc/<pid>/*id_map. + // + // Note that Sscanf doesn't consume the whole input, so we check for any + // trailing data with %c. That way, we can be sure the pattern matched + // /proc/$pid/ns/user _exactly_ iff n === 1. + if n, _ := fmt.Sscanf(nsPath, "/proc/%d/ns/user%c", &pid, &extra); n == 1 { + tryFastPath = pid > 0 + } + + for _, mapType := range []struct { + name string + idMap *[]configs.IDMap + }{ + {"uid_map", &uidMap}, + {"gid_map", &gidMap}, + } { + var mapData []byte + + if tryFastPath { + path := fmt.Sprintf("/proc/%d/%s", pid, mapType.name) + data, err := os.ReadFile(path) + if err != nil { + // Do not error out here -- we need to try the slow path if the + // fast path failed. + logrus.Debugf("failed to use fast path to read %s from userns %s (error: %s), falling back to slow userns-join path", mapType.name, nsPath, err) + } else { + mapData = data + } + } else { + logrus.Debugf("cannot use fast path to read %s from userns %s, falling back to slow userns-join path", mapType.name, nsPath) + } + + if mapData == nil { + // We have to actually join the namespace if we cannot take the + // fast path. The path is resolved with respect to the child + // process, so just use /proc/self. + data, err := spawnUserNamespaceCat(nsPath, "/proc/self/"+mapType.name) + if err != nil { + return nil, nil, err + } + mapData = data + } + idMap, err := parseIdmapData(mapData) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse %s of userns %s: %w", mapType.name, nsPath, err) + } + *mapType.idMap = idMap + } + + return uidMap, gidMap, nil +} + +// IsSameMapping returns whether or not the two id mappings are the same. Note +// that if the order of the mappings is different, or a mapping has been split, +// the mappings will be considered different. +func IsSameMapping(a, b []configs.IDMap) bool { + if len(a) != len(b) { + return false + } + for idx := range a { + if a[idx] != b[idx] { + return false + } + } + return true +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/tests/integration/run.bats new/runc-1.1.11/tests/integration/run.bats --- old/runc-1.1.10/tests/integration/run.bats 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/tests/integration/run.bats 2024-01-02 03:34:16.000000000 +0100 @@ -108,3 +108,60 @@ [ "$status" -eq 0 ] [ "$output" = "410" ] } + +@test "runc run [joining existing container namespaces]" { + # Create a detached container with the namespaces we want. We notably want + # to include userns, which requires config-related configuration. + if [ $EUID -eq 0 ]; then + update_config '.linux.namespaces += [{"type": "user"}] + | .linux.uidMappings += [{"containerID": 0, "hostID": 100000, "size": 100}] + | .linux.gidMappings += [{"containerID": 0, "hostID": 200000, "size": 200}]' + mkdir -p rootfs/{proc,sys,tmp} + fi + update_config '.process.args = ["sleep", "infinity"]' + + runc run -d --console-socket "$CONSOLE_SOCKET" target_ctr + [ "$status" -eq 0 ] + + # Modify our container's configuration such that it is just going to + # inherit all of the namespaces of the target container. + # + # NOTE: We cannot join the mount namespace of another container because of + # some quirks of the runtime-spec. In particular, we MUST pivot_root into + # root.path and root.path MUST be set in the config, so runc cannot just + # ignore root.path when joining namespaces (and root.path doesn't exist + # inside root.path, for obvious reasons). + # + # We could hack around this (create a copy of the rootfs inside the rootfs, + # or use a simpler mount namespace target), but those wouldn't be similar + # tests to the other namespace joining tests. + target_pid="$(__runc state target_ctr | jq .pid)" + update_config '.linux.namespaces |= map_values(.path = if .type == "mount" then "" else "/proc/'"$target_pid"'/ns/" + ({"network": "net", "mount": "mnt"}[.type] // .type) end)' + # Remove the userns configuration (it cannot be changed). + update_config '.linux |= (del(.uidMappings) | del(.gidMappings))' + + runc run -d --console-socket "$CONSOLE_SOCKET" attached_ctr + [ "$status" -eq 0 ] + + # Make sure there are two sleep processes in our container. + runc exec attached_ctr ps aux + [ "$status" -eq 0 ] + run -0 grep "sleep infinity" <<<"$output" + [ "${#lines[@]}" -eq 2 ] + + # ... that the userns mappings are the same... + runc exec attached_ctr cat /proc/self/uid_map + [ "$status" -eq 0 ] + if [ $EUID -eq 0 ]; then + grep -E '^\s+0\s+100000\s+100$' <<<"$output" + else + grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output" + fi + runc exec attached_ctr cat /proc/self/gid_map + [ "$status" -eq 0 ] + if [ $EUID -eq 0 ]; then + grep -E '^\s+0\s+200000\s+200$' <<<"$output" + else + grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output" + fi +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/tests/integration/userns.bats new/runc-1.1.11/tests/integration/userns.bats --- old/runc-1.1.10/tests/integration/userns.bats 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/tests/integration/userns.bats 2024-01-02 03:34:16.000000000 +0100 @@ -15,15 +15,25 @@ mkdir -p rootfs/{proc,sys,tmp} mkdir -p rootfs/tmp/mount-{1,2} + to_umount_list="$(mktemp "$BATS_RUN_TMPDIR/userns-mounts.XXXXXX")" if [ "$ROOTLESS" -eq 0 ]; then update_config ' .linux.namespaces += [{"type": "user"}] | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] - | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' + | .linux.gidMappings += [{"hostID": 200000, "containerID": 0, "size": 65534}] ' fi } function teardown() { teardown_bundle + + if [ -v to_umount_list ]; then + while read -r mount_path; do + umount -l "$mount_path" || : + rm -f "$mount_path" + done <"$to_umount_list" + rm -f "$to_umount_list" + unset to_umount_list + fi } @test "userns with simple mount" { @@ -83,3 +93,74 @@ runc exec test_busybox cat /sys/fs/cgroup/{pids,memory}/tasks [ "$status" -eq 0 ] } + +@test "userns join other container userns" { + # Create a detached container with the id-mapping we want. + update_config '.process.args = ["sleep", "infinity"]' + runc run -d --console-socket "$CONSOLE_SOCKET" target_userns + [ "$status" -eq 0 ] + + # Configure our container to attach to the first container's userns. + target_pid="$(__runc state target_userns | jq .pid)" + update_config '.linux.namespaces |= map(if .type == "user" then (.path = "/proc/'"$target_pid"'/ns/" + .type) else . end) + | del(.linux.uidMappings) + | del(.linux.gidMappings)' + runc run -d --console-socket "$CONSOLE_SOCKET" in_userns + [ "$status" -eq 0 ] + + runc exec in_userns cat /proc/self/uid_map + [ "$status" -eq 0 ] + if [ $EUID -eq 0 ]; then + grep -E '^\s+0\s+100000\s+65534$' <<<"$output" + else + grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output" + fi + + runc exec in_userns cat /proc/self/gid_map + [ "$status" -eq 0 ] + if [ $EUID -eq 0 ]; then + grep -E '^\s+0\s+200000\s+65534$' <<<"$output" + else + grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output" + fi +} + +@test "userns join other container userns [bind-mounted nsfd]" { + requires root + + # Create a detached container with the id-mapping we want. + update_config '.process.args = ["sleep", "infinity"]' + runc run -d --console-socket "$CONSOLE_SOCKET" target_userns + [ "$status" -eq 0 ] + + # Bind-mount the first containers userns nsfd to a different path, to + # exercise the non-fast-path (where runc has to join the userns to get the + # mappings). + target_pid="$(__runc state target_userns | jq .pid)" + userns_path=$(mktemp "$BATS_RUN_TMPDIR/userns.XXXXXX") + mount --bind "/proc/$target_pid/ns/user" "$userns_path" + echo "$userns_path" >>"$to_umount_list" + + # Configure our container to attach to the first container's userns. + update_config '.linux.namespaces |= map(if .type == "user" then (.path = "'"$userns_path"'") else . end) + | del(.linux.uidMappings) + | del(.linux.gidMappings)' + runc run -d --console-socket "$CONSOLE_SOCKET" in_userns + [ "$status" -eq 0 ] + + runc exec in_userns cat /proc/self/uid_map + [ "$status" -eq 0 ] + if [ $EUID -eq 0 ]; then + grep -E '^\s+0\s+100000\s+65534$' <<<"$output" + else + grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output" + fi + + runc exec in_userns cat /proc/self/gid_map + [ "$status" -eq 0 ] + if [ $EUID -eq 0 ]; then + grep -E '^\s+0\s+200000\s+65534$' <<<"$output" + else + grep -E '^\s+0\s+'$EUID'\s+1$' <<<"$output" + fi +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/vendor/github.com/cyphar/filepath-securejoin/.travis.yml new/runc-1.1.11/vendor/github.com/cyphar/filepath-securejoin/.travis.yml --- old/runc-1.1.10/vendor/github.com/cyphar/filepath-securejoin/.travis.yml 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/vendor/github.com/cyphar/filepath-securejoin/.travis.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,21 +0,0 @@ -# Copyright (C) 2017 SUSE LLC. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -language: go -go: - - 1.13.x - - 1.16.x - - tip -arch: - - AMD64 - - ppc64le -os: - - linux - - osx - -script: - - go test -cover -v ./... - -notifications: - email: false diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/vendor/github.com/cyphar/filepath-securejoin/README.md new/runc-1.1.11/vendor/github.com/cyphar/filepath-securejoin/README.md --- old/runc-1.1.10/vendor/github.com/cyphar/filepath-securejoin/README.md 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/vendor/github.com/cyphar/filepath-securejoin/README.md 2024-01-02 03:34:16.000000000 +0100 @@ -1,6 +1,6 @@ ## `filepath-securejoin` ## -[](https://travis-ci.org/cyphar/filepath-securejoin) +[](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml) An implementation of `SecureJoin`, a [candidate for inclusion in the Go standard library][go#20126]. The purpose of this function is to be a "secure" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/vendor/github.com/cyphar/filepath-securejoin/VERSION new/runc-1.1.11/vendor/github.com/cyphar/filepath-securejoin/VERSION --- old/runc-1.1.10/vendor/github.com/cyphar/filepath-securejoin/VERSION 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/vendor/github.com/cyphar/filepath-securejoin/VERSION 2024-01-02 03:34:16.000000000 +0100 @@ -1 +1 @@ -0.2.3 +0.2.4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/vendor/github.com/cyphar/filepath-securejoin/join.go new/runc-1.1.11/vendor/github.com/cyphar/filepath-securejoin/join.go --- old/runc-1.1.10/vendor/github.com/cyphar/filepath-securejoin/join.go 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/vendor/github.com/cyphar/filepath-securejoin/join.go 2024-01-02 03:34:16.000000000 +0100 @@ -39,17 +39,27 @@ // components in the returned string are not modified (in other words are not // replaced with symlinks on the filesystem) after this function has returned. // Such a symlink race is necessarily out-of-scope of SecureJoin. +// +// Volume names in unsafePath are always discarded, regardless if they are +// provided via direct input or when evaluating symlinks. Therefore: +// +// "C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt" func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) { // Use the os.* VFS implementation if none was specified. if vfs == nil { vfs = osVFS{} } + unsafePath = filepath.FromSlash(unsafePath) var path bytes.Buffer n := 0 for unsafePath != "" { if n > 255 { - return "", &os.PathError{Op: "SecureJoin", Path: root + "/" + unsafePath, Err: syscall.ELOOP} + return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP} + } + + if v := filepath.VolumeName(unsafePath); v != "" { + unsafePath = unsafePath[len(v):] } // Next path component, p. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/runc-1.1.10/vendor/modules.txt new/runc-1.1.11/vendor/modules.txt --- old/runc-1.1.10/vendor/modules.txt 2023-11-01 08:01:51.000000000 +0100 +++ new/runc-1.1.11/vendor/modules.txt 2024-01-02 03:34:16.000000000 +0100 @@ -20,7 +20,7 @@ # github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d ## explicit; go 1.12 github.com/cpuguy83/go-md2man/v2/md2man -# github.com/cyphar/filepath-securejoin v0.2.3 +# github.com/cyphar/filepath-securejoin v0.2.4 ## explicit; go 1.13 github.com/cyphar/filepath-securejoin # github.com/docker/go-units v0.4.0
