Archive package allows chmod of file outside of unpack target directory
Description
containerd is a container runtime. A bug was found in containerd versions prior to 1.4.8 and 1.5.4 where pulling and extracting a specially-crafted container image can result in Unix file permission changes for existing files in the host’s filesystem. Changes to file permissions can deny access to the expected owner of the file, widen access to others, or set extended bits like setuid, setgid, and sticky. This bug does not directly allow files to be read, modified, or executed without an additional cooperating process. This bug has been fixed in containerd 1.5.4 and 1.4.8. As a workaround, ensure that users only pull images from trusted sources. Linux security modules (LSMs) like SELinux and AppArmor can limit the files potentially affected by this bug through policies and profiles that prevent containerd from interacting with specific files.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/containerd/containerdGo | < 1.4.8 | 1.4.8 |
github.com/containerd/containerdGo | >= 1.5.0, < 1.5.4 | 1.5.4 |
Affected products
1- Range: <= 1.4.7
Patches
27ad08c69e09eMerge pull request from GHSA-c72p-9xmj-rx3w
6 files changed · +71 −21
archive/tar_freebsd.go+13 −1 modified@@ -18,7 +18,11 @@ package archive -import "golang.org/x/sys/unix" +import ( + "os" + + "golang.org/x/sys/unix" +) // mknod wraps unix.Mknod. FreeBSD's unix.Mknod signature is different from // other Unix and Unix-like operating systems. @@ -34,3 +38,11 @@ func lsetxattrCreate(link string, attr string, data []byte) error { } return err } + +func lchmod(path string, mode os.FileMode) error { + err := unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), unix.AT_SYMLINK_NOFOLLOW) + if err != nil { + err = &os.PathError{Op: "lchmod", Path: path, Err: err} + } + return err +}
archive/tar.go+2 −3 modified@@ -393,9 +393,8 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header } } - // There is no LChmod, so ignore mode for symlink. Also, this - // must happen after chown, as that can modify the file mode - if err := handleLChmod(hdr, path, hdrInfo); err != nil { + // call lchmod after lchown since lchown can modify the file mode + if err := lchmod(path, hdrInfo.Mode()); err != nil { return err }
archive/tar_mostunix.go+20 −1 modified@@ -18,7 +18,11 @@ package archive -import "golang.org/x/sys/unix" +import ( + "os" + + "golang.org/x/sys/unix" +) // mknod wraps Unix.Mknod and casts dev to int func mknod(path string, mode uint32, dev uint64) error { @@ -34,3 +38,18 @@ func lsetxattrCreate(link string, attr string, data []byte) error { } return err } + +// lchmod checks for symlink and changes the mode if not a symlink +func lchmod(path string, mode os.FileMode) error { + fi, err := os.Lstat(path) + if err != nil { + return err + } + + if fi.Mode()&os.ModeSymlink == 0 { + if err := os.Chmod(path, mode); err != nil { + return err + } + } + return nil +}
archive/tar_test.go+35 −0 modified@@ -243,6 +243,11 @@ func TestBreakouts(t *testing.T) { return nil } errFileDiff := errors.New("files differ") + td, err := ioutil.TempDir("", "test-breakouts-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(td) isSymlinkFile := func(f string) func(string) error { return func(root string) error { @@ -744,6 +749,36 @@ func TestBreakouts(t *testing.T) { // resolution ends up just removing etc validator: fileNotExists("etc/passwd"), }, + { + + name: "HardlinkSymlinkChmod", + w: func() tartest.WriterToTar { + p := filepath.Join(td, "perm400") + if err := ioutil.WriteFile(p, []byte("..."), 0400); err != nil { + t.Fatal(err) + } + ep := filepath.Join(td, "also-exists-outside-root") + if err := ioutil.WriteFile(ep, []byte("..."), 0640); err != nil { + t.Fatal(err) + } + + return tartest.TarAll( + tc.Symlink(p, ep), + tc.Link(ep, "sketchylink"), + ) + }(), + validator: func(string) error { + p := filepath.Join(td, "perm400") + fi, err := os.Lstat(p) + if err != nil { + return err + } + if perm := fi.Mode() & os.ModePerm; perm != 0400 { + return errors.Errorf("%s perm changed from 0400 to %04o", p, perm) + } + return nil + }, + }, } for _, bo := range breakouts {
archive/tar_unix.go+0 −15 modified@@ -111,21 +111,6 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { return mknod(path, mode, unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))) } -func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { - if hdr.Typeflag == tar.TypeLink { - if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { - if err := os.Chmod(path, hdrInfo.Mode()); err != nil && !os.IsNotExist(err) { - return err - } - } - } else if hdr.Typeflag != tar.TypeSymlink { - if err := os.Chmod(path, hdrInfo.Mode()); err != nil { - return err - } - } - return nil -} - func getxattr(path, attr string) ([]byte, error) { b, err := sysx.LGetxattr(path, attr) if err == unix.ENOTSUP || err == sysx.ENODATA {
archive/tar_windows.go+1 −1 modified@@ -98,7 +98,7 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { return nil } -func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { +func lchmod(path string, mode os.FileMode) error { return nil }
22e9a70c71efMerge pull request from GHSA-c72p-9xmj-rx3w
2 files changed · +36 −1
archive/tar_test.go+35 −0 modified@@ -243,6 +243,11 @@ func TestBreakouts(t *testing.T) { return nil } errFileDiff := errors.New("files differ") + td, err := ioutil.TempDir("", "test-breakouts-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(td) isSymlinkFile := func(f string) func(string) error { return func(root string) error { @@ -744,6 +749,36 @@ func TestBreakouts(t *testing.T) { // resolution ends up just removing etc validator: fileNotExists("etc/passwd"), }, + { + + name: "HardlinkSymlinkChmod", + w: func() tartest.WriterToTar { + p := filepath.Join(td, "perm400") + if err := ioutil.WriteFile(p, []byte("..."), 0400); err != nil { + t.Fatal(err) + } + ep := filepath.Join(td, "also-exists-outside-root") + if err := ioutil.WriteFile(ep, []byte("..."), 0640); err != nil { + t.Fatal(err) + } + + return tartest.TarAll( + tc.Symlink(p, ep), + tc.Link(ep, "sketchylink"), + ) + }(), + validator: func(string) error { + p := filepath.Join(td, "perm400") + fi, err := os.Lstat(p) + if err != nil { + return err + } + if perm := fi.Mode() & os.ModePerm; perm != 0400 { + return errors.Errorf("%s perm changed from 0400 to %04o", p, perm) + } + return nil + }, + }, } for _, bo := range breakouts {
archive/tar_unix.go+1 −1 modified@@ -113,7 +113,7 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { if hdr.Typeflag == tar.TypeLink { - if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { + if fi, err := os.Lstat(path); err == nil && (fi.Mode()&os.ModeSymlink == 0) { if err := os.Chmod(path, hdrInfo.Mode()); err != nil && !os.IsNotExist(err) { return err }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-c72p-9xmj-rx3wghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/DDMNDPJJTP3J5GOEDB66F6MGXUTRG3Y3/mitrevendor-advisory
- nvd.nist.gov/vuln/detail/CVE-2021-32760ghsaADVISORY
- security.gentoo.org/glsa/202401-31ghsavendor-advisoryWEB
- github.com/containerd/containerd/commit/22e9a70c71eff6507be71955947a611f2ed91e6cghsaWEB
- github.com/containerd/containerd/commit/7ad08c69e09ee4930a48dbf2aab3cd612458617fghsaWEB
- github.com/containerd/containerd/releases/tag/v1.4.8ghsaWEB
- github.com/containerd/containerd/releases/tag/v1.5.4ghsaWEB
- github.com/containerd/containerd/security/advisories/GHSA-c72p-9xmj-rx3wghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/DDMNDPJJTP3J5GOEDB66F6MGXUTRG3Y3ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DDMNDPJJTP3J5GOEDB66F6MGXUTRG3Y3ghsaWEB
News mentions
0No linked articles in our index yet.