Skip to content

Commit 93a61f2

Browse files
committed
move FileInfoHeaderNoLookups to separate tarheader package
This aligns with how containerd organizes the code, and allows consuming this function without requiring all the other code. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent c0a4ff3 commit 93a61f2

File tree

6 files changed

+152
-112
lines changed

6 files changed

+152
-112
lines changed

archive.go

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/moby/sys/user"
2121

2222
"github.com/moby/go-archive/compression"
23+
"github.com/moby/go-archive/tarheader"
2324
)
2425

2526
// ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a
@@ -234,71 +235,11 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
234235
return pipeReader
235236
}
236237

237-
// assert that we implement [tar.FileInfoNames].
238-
var _ tar.FileInfoNames = (*nosysFileInfo)(nil)
239-
240-
// nosysFileInfo hides the system-dependent info of the wrapped FileInfo to
241-
// prevent tar.FileInfoHeader from introspecting it and potentially calling into
242-
// glibc.
243-
//
244-
// It implements [tar.FileInfoNames] to further prevent [tar.FileInfoHeader]
245-
// from performing any lookups on go1.23 and up. see https://go.dev/issue/50102
246-
type nosysFileInfo struct {
247-
os.FileInfo
248-
}
249-
250-
// Uname stubs out looking up username. It implements [tar.FileInfoNames]
251-
// to prevent [tar.FileInfoHeader] from loading libraries to perform
252-
// username lookups.
253-
func (fi nosysFileInfo) Uname() (string, error) {
254-
return "", nil
255-
}
256-
257-
// Gname stubs out looking up group-name. It implements [tar.FileInfoNames]
258-
// to prevent [tar.FileInfoHeader] from loading libraries to perform
259-
// username lookups.
260-
func (fi nosysFileInfo) Gname() (string, error) {
261-
return "", nil
262-
}
263-
264-
func (fi nosysFileInfo) Sys() interface{} {
265-
// A Sys value of type *tar.Header is safe as it is system-independent.
266-
// The tar.FileInfoHeader function copies the fields into the returned
267-
// header without performing any OS lookups.
268-
if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok {
269-
return sys
270-
}
271-
return nil
272-
}
273-
274-
// sysStat, if non-nil, populates hdr from system-dependent fields of fi.
275-
var sysStat func(fi os.FileInfo, hdr *tar.Header) error
276-
277238
// FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi.
278239
//
279-
// Compared to the archive/tar.FileInfoHeader function, this function is safe to
280-
// call from a chrooted process as it does not populate fields which would
281-
// require operating system lookups. It behaves identically to
282-
// tar.FileInfoHeader when fi is a FileInfo value returned from
283-
// tar.Header.FileInfo().
284-
//
285-
// When fi is a FileInfo for a native file, such as returned from os.Stat() and
286-
// os.Lstat(), the returned Header value differs from one returned from
287-
// tar.FileInfoHeader in the following ways. The Uname and Gname fields are not
288-
// set as OS lookups would be required to populate them. The AccessTime and
289-
// ChangeTime fields are not currently set (not yet implemented) although that
290-
// is subject to change. Callers which require the AccessTime or ChangeTime
291-
// fields to be zeroed should explicitly zero them out in the returned Header
292-
// value to avoid any compatibility issues in the future.
240+
// Deprecated: use [tarheader.FileInfoHeaderNoLookups].
293241
func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
294-
hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link)
295-
if err != nil {
296-
return nil, err
297-
}
298-
if sysStat != nil {
299-
return hdr, sysStat(fi, hdr)
300-
}
301-
return hdr, nil
242+
return tarheader.FileInfoHeaderNoLookups(fi, link)
302243
}
303244

304245
// FileInfoHeader creates a populated Header from fi.
@@ -309,7 +250,7 @@ func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
309250
// precision, and the Uname and Gname fields are only set when fi is a FileInfo
310251
// value returned from tar.Header.FileInfo().
311252
func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) {
312-
hdr, err := FileInfoHeaderNoLookups(fi, link)
253+
hdr, err := tarheader.FileInfoHeaderNoLookups(fi, link)
313254
if err != nil {
314255
return nil, err
315256
}
@@ -1177,7 +1118,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
11771118
}
11781119
defer srcF.Close()
11791120

1180-
hdr, err := FileInfoHeaderNoLookups(srcSt, "")
1121+
hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "")
11811122
if err != nil {
11821123
return err
11831124
}

archive_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,12 +1123,3 @@ func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expec
11231123
assert.Check(t, err)
11241124
return string(content)
11251125
}
1126-
1127-
func TestNosysFileInfo(t *testing.T) {
1128-
st, err := os.Stat("archive_test.go")
1129-
assert.NilError(t, err)
1130-
h, err := tar.FileInfoHeader(nosysFileInfo{st}, "")
1131-
assert.NilError(t, err)
1132-
assert.Check(t, h.Uname == "")
1133-
assert.Check(t, h.Gname == "")
1134-
}

archive_unix.go

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,12 @@ import (
77
"errors"
88
"os"
99
"path/filepath"
10-
"runtime"
1110
"strings"
1211
"syscall"
1312

1413
"golang.org/x/sys/unix"
1514
)
1615

17-
func init() {
18-
sysStat = statUnix
19-
}
20-
2116
// addLongPathPrefix adds the Windows long path prefix to the path provided if
2217
// it does not already have it. It is a no-op on platforms other than Windows.
2318
func addLongPathPrefix(srcPath string) string {
@@ -38,40 +33,6 @@ func chmodTarEntry(perm os.FileMode) os.FileMode {
3833
return perm // noop for unix as golang APIs provide perm bits correctly
3934
}
4035

41-
// statUnix populates hdr from system-dependent fields of fi without performing
42-
// any OS lookups.
43-
func statUnix(fi os.FileInfo, hdr *tar.Header) error {
44-
// Devmajor and Devminor are only needed for special devices.
45-
46-
// In FreeBSD, RDev for regular files is -1 (unless overridden by FS):
47-
// https://cgit.freebsd.org/src/tree/sys/kern/vfs_default.c?h=stable/13#n1531
48-
// (NODEV is -1: https://cgit.freebsd.org/src/tree/sys/sys/param.h?h=stable/13#n241).
49-
50-
// ZFS in particular does not override the default:
51-
// https://cgit.freebsd.org/src/tree/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c?h=stable/13#n2027
52-
53-
// Since `Stat_t.Rdev` is uint64, the cast turns -1 into (2^64 - 1).
54-
// Such large values cannot be encoded in a tar header.
55-
if runtime.GOOS == "freebsd" && hdr.Typeflag != tar.TypeBlock && hdr.Typeflag != tar.TypeChar {
56-
return nil
57-
}
58-
s, ok := fi.Sys().(*syscall.Stat_t)
59-
if !ok {
60-
return nil
61-
}
62-
63-
hdr.Uid = int(s.Uid)
64-
hdr.Gid = int(s.Gid)
65-
66-
if s.Mode&unix.S_IFBLK != 0 ||
67-
s.Mode&unix.S_IFCHR != 0 {
68-
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert
69-
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert
70-
}
71-
72-
return nil
73-
}
74-
7536
func getInodeFromStat(stat interface{}) (uint64, error) {
7637
s, ok := stat.(*syscall.Stat_t)
7738
if !ok {

tarheader/tarheader.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package tarheader
2+
3+
import (
4+
"archive/tar"
5+
"os"
6+
)
7+
8+
// assert that we implement [tar.FileInfoNames].
9+
var _ tar.FileInfoNames = (*nosysFileInfo)(nil)
10+
11+
// nosysFileInfo hides the system-dependent info of the wrapped FileInfo to
12+
// prevent tar.FileInfoHeader from introspecting it and potentially calling into
13+
// glibc.
14+
//
15+
// It implements [tar.FileInfoNames] to further prevent [tar.FileInfoHeader]
16+
// from performing any lookups on go1.23 and up. see https://go.dev/issue/50102
17+
type nosysFileInfo struct {
18+
os.FileInfo
19+
}
20+
21+
// Uname stubs out looking up username. It implements [tar.FileInfoNames]
22+
// to prevent [tar.FileInfoHeader] from loading libraries to perform
23+
// username lookups.
24+
func (fi nosysFileInfo) Uname() (string, error) {
25+
return "", nil
26+
}
27+
28+
// Gname stubs out looking up group-name. It implements [tar.FileInfoNames]
29+
// to prevent [tar.FileInfoHeader] from loading libraries to perform
30+
// username lookups.
31+
func (fi nosysFileInfo) Gname() (string, error) {
32+
return "", nil
33+
}
34+
35+
func (fi nosysFileInfo) Sys() interface{} {
36+
// A Sys value of type *tar.Header is safe as it is system-independent.
37+
// The tar.FileInfoHeader function copies the fields into the returned
38+
// header without performing any OS lookups.
39+
if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok {
40+
return sys
41+
}
42+
return nil
43+
}
44+
45+
// sysStat, if non-nil, populates hdr from system-dependent fields of fi.
46+
var sysStat func(fi os.FileInfo, hdr *tar.Header) error
47+
48+
// FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi.
49+
//
50+
// Compared to the archive/tar.FileInfoHeader function, this function is safe to
51+
// call from a chrooted process as it does not populate fields which would
52+
// require operating system lookups. It behaves identically to
53+
// tar.FileInfoHeader when fi is a FileInfo value returned from
54+
// tar.Header.FileInfo().
55+
//
56+
// When fi is a FileInfo for a native file, such as returned from os.Stat() and
57+
// os.Lstat(), the returned Header value differs from one returned from
58+
// tar.FileInfoHeader in the following ways. The Uname and Gname fields are not
59+
// set as OS lookups would be required to populate them. The AccessTime and
60+
// ChangeTime fields are not currently set (not yet implemented) although that
61+
// is subject to change. Callers which require the AccessTime or ChangeTime
62+
// fields to be zeroed should explicitly zero them out in the returned Header
63+
// value to avoid any compatibility issues in the future.
64+
func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
65+
hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link)
66+
if err != nil {
67+
return nil, err
68+
}
69+
if sysStat != nil {
70+
return hdr, sysStat(fi, hdr)
71+
}
72+
return hdr, nil
73+
}

tarheader/tarheader_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package tarheader
2+
3+
import (
4+
"archive/tar"
5+
"os"
6+
"testing"
7+
)
8+
9+
func TestNosysFileInfo(t *testing.T) {
10+
st, err := os.Stat("tarheader_test.go")
11+
if err != nil {
12+
t.Fatal(err)
13+
}
14+
h, err := tar.FileInfoHeader(nosysFileInfo{st}, "")
15+
if err != nil {
16+
t.Fatal(err)
17+
}
18+
if h.Uname != "" {
19+
t.Errorf("uname should be empty; got %v", h.Uname)
20+
}
21+
if h.Gname != "" {
22+
t.Errorf("gname should be empty; got %v", h.Uname)
23+
}
24+
}

tarheader/tarheader_unix.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//go:build !windows
2+
3+
package tarheader
4+
5+
import (
6+
"archive/tar"
7+
"os"
8+
"runtime"
9+
"syscall"
10+
11+
"golang.org/x/sys/unix"
12+
)
13+
14+
func init() {
15+
sysStat = statUnix
16+
}
17+
18+
// statUnix populates hdr from system-dependent fields of fi without performing
19+
// any OS lookups.
20+
func statUnix(fi os.FileInfo, hdr *tar.Header) error {
21+
// Devmajor and Devminor are only needed for special devices.
22+
23+
// In FreeBSD, RDev for regular files is -1 (unless overridden by FS):
24+
// https://cgit.freebsd.org/src/tree/sys/kern/vfs_default.c?h=stable/13#n1531
25+
// (NODEV is -1: https://cgit.freebsd.org/src/tree/sys/sys/param.h?h=stable/13#n241).
26+
27+
// ZFS in particular does not override the default:
28+
// https://cgit.freebsd.org/src/tree/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c?h=stable/13#n2027
29+
30+
// Since `Stat_t.Rdev` is uint64, the cast turns -1 into (2^64 - 1).
31+
// Such large values cannot be encoded in a tar header.
32+
if runtime.GOOS == "freebsd" && hdr.Typeflag != tar.TypeBlock && hdr.Typeflag != tar.TypeChar {
33+
return nil
34+
}
35+
s, ok := fi.Sys().(*syscall.Stat_t)
36+
if !ok {
37+
return nil
38+
}
39+
40+
hdr.Uid = int(s.Uid)
41+
hdr.Gid = int(s.Gid)
42+
43+
if s.Mode&unix.S_IFBLK != 0 ||
44+
s.Mode&unix.S_IFCHR != 0 {
45+
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert
46+
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert
47+
}
48+
49+
return nil
50+
}

0 commit comments

Comments
 (0)