Skip to content

Commit 9ec1e6e

Browse files
authored
Merge pull request #8 from thaJeztah/move_header
move FileInfoHeaderNoLookups to separate tarheader package
2 parents c0a4ff3 + 3d4966f commit 9ec1e6e

File tree

7 files changed

+154
-112
lines changed

7 files changed

+154
-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: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
// FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi.
46+
//
47+
// Compared to the archive/tar.FileInfoHeader function, this function is safe to
48+
// call from a chrooted process as it does not populate fields which would
49+
// require operating system lookups. It behaves identically to
50+
// tar.FileInfoHeader when fi is a FileInfo value returned from
51+
// tar.Header.FileInfo().
52+
//
53+
// When fi is a FileInfo for a native file, such as returned from os.Stat() and
54+
// os.Lstat(), the returned Header value differs from one returned from
55+
// tar.FileInfoHeader in the following ways. The Uname and Gname fields are not
56+
// set as OS lookups would be required to populate them. The AccessTime and
57+
// ChangeTime fields are not currently set (not yet implemented) although that
58+
// is subject to change. Callers which require the AccessTime or ChangeTime
59+
// fields to be zeroed should explicitly zero them out in the returned Header
60+
// value to avoid any compatibility issues in the future.
61+
func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
62+
hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link)
63+
if err != nil {
64+
return nil, err
65+
}
66+
return hdr, sysStat(fi, hdr)
67+
}

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

tarheader/tarheader_windows.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package tarheader
2+
3+
import (
4+
"archive/tar"
5+
"os"
6+
)
7+
8+
// sysStat populates hdr from system-dependent fields of fi without performing
9+
// any OS lookups. It is a no-op on Windows.
10+
func sysStat(os.FileInfo, *tar.Header) error {
11+
return nil
12+
}

0 commit comments

Comments
 (0)