Skip to content

Commit dedf0f6

Browse files
committed
fix(archiver): use tar.FileInfoNames to avoid CGO user/group lookups
1 parent 96831b5 commit dedf0f6

1 file changed

Lines changed: 37 additions & 39 deletions

File tree

pkg/archiver/archiver.go

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,38 +23,17 @@ import (
2323
"os"
2424
"path/filepath"
2525
"strings"
26-
"syscall"
2726
)
2827

29-
// buildTarHeader creates a tar header from FileInfo without using CGO-based
30-
// os/user.LookupGroupId or os/user.LookupUserId. This avoids a segfault in
31-
// statically linked CGO binaries caused by glibc NSS (getgrgid_r) being
32-
// incompatible with static linking across different glibc versions.
33-
// See: https://github.com/modelpack/modctl/issues/285
34-
func buildTarHeader(info os.FileInfo) (*tar.Header, error) {
35-
header := &tar.Header{
36-
Name: info.Name(),
37-
Size: info.Size(),
38-
Mode: int64(info.Mode()),
39-
ModTime: info.ModTime(),
40-
}
41-
42-
// Set file type flag.
43-
if info.IsDir() {
44-
header.Typeflag = tar.TypeDir
45-
} else {
46-
header.Typeflag = tar.TypeReg
47-
}
28+
type noLookupFileInfo struct {
29+
os.FileInfo
30+
}
4831

49-
// Safely extract UID/GID from syscall.Stat_t without CGO user/group name lookup.
50-
// We intentionally leave Uname/Gname empty to avoid os/user CGO calls entirely.
51-
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
52-
header.Uid = int(stat.Uid)
53-
header.Gid = int(stat.Gid)
54-
}
32+
// Uname returns an empty user name to skip CGO-based uid→name lookup.
33+
func (n noLookupFileInfo) Uname() (string, error) { return "", nil }
5534

56-
return header, nil
57-
}
35+
// Gname returns an empty group name to skip CGO-based gid→name lookup.
36+
func (n noLookupFileInfo) Gname() (string, error) { return "", nil }
5837

5938
// Tar creates a tar archive of the specified path (file or directory)
6039
// and returns the content as a stream. For individual files, it preserves
@@ -81,24 +60,35 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
8160
return err
8261
}
8362

84-
// Create a relative path for the tar file header.
85-
relPath, err := filepath.Rel(workDir, path)
86-
if err != nil {
87-
return fmt.Errorf("failed to get relative path: %w", err)
63+
// Resolve symlink target if needed.
64+
link := ""
65+
if info.Mode()&os.ModeSymlink != 0 {
66+
link, err = os.Readlink(path)
67+
if err != nil {
68+
return fmt.Errorf("failed to read symlink %s: %w", path, err)
69+
}
8870
}
8971

90-
header, err := buildTarHeader(info)
72+
header, err := tar.FileInfoHeader(noLookupFileInfo{info}, link)
9173
if err != nil {
9274
return fmt.Errorf("failed to create tar header: %w", err)
9375
}
9476

95-
// Set the header name to preserve directory structure.
77+
// FileInfoHeader only fills the base name; set the full relative path.
78+
relPath, err := filepath.Rel(workDir, path)
79+
if err != nil {
80+
return fmt.Errorf("failed to get relative path: %w", err)
81+
}
9682
header.Name = relPath
83+
if info.IsDir() {
84+
header.Name += "/"
85+
}
86+
9787
if err := tw.WriteHeader(header); err != nil {
9888
return fmt.Errorf("failed to write header: %w", err)
9989
}
10090

101-
if !info.IsDir() {
91+
if !info.IsDir() && info.Mode()&os.ModeSymlink == 0 {
10292
file, err := os.Open(path)
10393
if err != nil {
10494
return fmt.Errorf("failed to open file %s: %w", path, err)
@@ -126,21 +116,29 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
126116
}
127117
defer file.Close()
128118

129-
header, err := buildTarHeader(info)
119+
// Resolve symlink target if needed.
120+
link := ""
121+
if info.Mode()&os.ModeSymlink != 0 {
122+
link, err = os.Readlink(srcPath)
123+
if err != nil {
124+
pw.CloseWithError(fmt.Errorf("failed to read symlink %s: %w", srcPath, err))
125+
return
126+
}
127+
}
128+
header, err := tar.FileInfoHeader(noLookupFileInfo{info}, link)
130129
if err != nil {
131130
pw.CloseWithError(fmt.Errorf("failed to create tar header: %w", err))
132131
return
133132
}
134133

135-
// Use relative path as the header name to preserve directory structure.
134+
// FileInfoHeader only fills the base name; set the full relative path.
136135
relPath, err := filepath.Rel(workDir, srcPath)
137136
if err != nil {
138137
pw.CloseWithError(fmt.Errorf("failed to get relative path: %w", err))
139138
return
140139
}
141-
142-
// Use the relative path (including directories) as the header name.
143140
header.Name = relPath
141+
144142
if err := tw.WriteHeader(header); err != nil {
145143
pw.CloseWithError(fmt.Errorf("failed to write header: %w", err))
146144
return

0 commit comments

Comments
 (0)