Skip to content

Commit f07b511

Browse files
committed
fix: honor --userns in unsquashfs wrapping
If singularity is executed with `--userns/-u` then it should also use a user namespace where it executes `unsquashfs` in a wrapped manner. Previously the `unsquashfs` wrapping was without `--userns/-u` in a setuid installation. This caused extraction to fail from within a non-root-mapped user namespace (e.g. `unshare -c`). Part of #2698
1 parent 877b762 commit f07b511

File tree

8 files changed

+32
-23
lines changed

8 files changed

+32
-23
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
the original image.
2020
- Fix `target: no such file or directory` error in native mode when extracting
2121
layers from certain OCI images that manipulate hard links across layers.
22-
- Fix extraction of OCI layers when run in a root mapped user namespace
22+
- Fix extraction of OCI layers when run in a root mapped user namespace
2323
(e.g.. `unshare -r`).
24+
- Use user namespace for wrapping of `unsquashfs` when singularity is run with
25+
`--userns / -u` flag. Fixes temporary sandbox extraction of images in non-root
26+
mapped user namespace (e.g. `unshare -c`).
2427

2528
## 4.1.1 \[2024-02-01\]
2629

internal/pkg/build/sources/packer_sif.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func unpackSIF(b *types.Bundle, img *image.Image) (err error) {
5151
return fmt.Errorf("could not extract root filesystem: %s", err)
5252
}
5353

54-
s := unpacker.NewSquashfs()
54+
s := unpacker.NewSquashfs(false)
5555

5656
// extract root filesystem
5757
if err := s.ExtractAll(reader, b.RootfsPath); err != nil {

internal/pkg/build/sources/packer_squashfs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func (p *SquashfsPacker) Pack(context.Context) (*types.Bundle, error) {
2929
return nil, fmt.Errorf("could not extract root filesystem: %s", err)
3030
}
3131

32-
s := unpacker.NewSquashfs()
32+
s := unpacker.NewSquashfs(false)
3333

3434
// extract root filesystem
3535
if err := s.ExtractAll(reader, p.b.RootfsPath); err != nil {

internal/pkg/image/unpacker/squashfs.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ const (
2626
excludeDevRegex = `^(.{0}[^d]|.{1}[^e]|.{2}[^v]|.{3}[^\x2f]).*$`
2727
)
2828

29-
var cmdFunc func(unsquashfs string, dest string, filename string, filter string, opts ...string) (*exec.Cmd, error)
29+
var cmdFunc func(squashfs *Squashfs, dest string, filename string, filter string, opts ...string) (*exec.Cmd, error)
3030

3131
// unsquashfsCmd is the command instance for executing unsquashfs command
3232
// in a non sandboxed environment when this package is used for unit tests.
33-
func unsquashfsCmd(unsquashfs string, dest string, filename string, filter string, opts ...string) (*exec.Cmd, error) {
33+
func unsquashfsCmd(squashfs *Squashfs, dest string, filename string, filter string, opts ...string) (*exec.Cmd, error) {
3434
args := []string{}
3535
args = append(args, opts...)
3636
// remove the destination directory if any, if the directory is
@@ -49,18 +49,23 @@ func unsquashfsCmd(unsquashfs string, dest string, filename string, filter strin
4949
args = append(args, filter)
5050
}
5151

52-
sylog.Debugf("Calling %s %v", unsquashfs, args)
53-
return exec.Command(unsquashfs, args...), nil
52+
sylog.Debugf("Calling %s %v", squashfs.UnsquashfsPath, args)
53+
return exec.Command(squashfs.UnsquashfsPath, args...), nil
5454
}
5555

5656
// Squashfs represents a squashfs unpacker.
5757
type Squashfs struct {
58+
// Path to the unsquashfs executable
5859
UnsquashfsPath string
60+
// ForceUserns sets --userns when unsquashfs is being run wrapped by singularity.
61+
ForceUserns bool
5962
}
6063

6164
// NewSquashfs initializes and returns a Squahfs unpacker instance
62-
func NewSquashfs() *Squashfs {
63-
s := &Squashfs{}
65+
func NewSquashfs(userns bool) *Squashfs {
66+
s := &Squashfs{
67+
ForceUserns: userns,
68+
}
6469
s.UnsquashfsPath, _ = bin.FindBin("unsquashfs")
6570
return s
6671
}
@@ -112,7 +117,7 @@ func (s *Squashfs) extract(files []string, reader io.Reader, dest string) (err e
112117
if err != nil {
113118
return fmt.Errorf("could not get host UID: %s", err)
114119
}
115-
rootless := hostuid != 0
120+
rootless := s.ForceUserns || hostuid != 0
116121

117122
// Does our target filesystem support user xattrs?
118123
ok, err := TestUserXattr(filepath.Dir(dest))
@@ -155,7 +160,7 @@ func (s *Squashfs) extract(files []string, reader io.Reader, dest string) (err e
155160

156161
// Now run unsquashfs with our 'best' options
157162
sylog.Debugf("Trying unsquashfs options: %v", opts)
158-
cmd, err := cmdFunc(s.UnsquashfsPath, dest, filename, filter, opts...)
163+
cmd, err := cmdFunc(s, dest, filename, filter, opts...)
159164
if err != nil {
160165
return fmt.Errorf("command error: %s", err)
161166
}

internal/pkg/image/unpacker/squashfs_singularity.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func parseLibraryBinds(buf io.Reader) ([]libBind, error) {
144144

145145
// unsquashfsSandboxCmd is the command instance for executing unsquashfs command
146146
// in a sandboxed environment with singularity.
147-
func unsquashfsSandboxCmd(unsquashfs string, dest string, filename string, filter string, opts ...string) (*exec.Cmd, error) {
147+
func unsquashfsSandboxCmd(squashfs *Squashfs, dest string, filename string, filter string, opts ...string) (*exec.Cmd, error) {
148148
const (
149149
// will contain both dest and filename inside the sandbox
150150
rootfsImageDir = "/image"
@@ -186,9 +186,6 @@ func unsquashfsSandboxCmd(unsquashfs string, dest string, filename string, filte
186186
}
187187
}
188188

189-
// the decision to use user namespace is left to singularity
190-
// which will detect automatically depending of the configuration
191-
// what workflow it could use
192189
args := []string{
193190
"exec",
194191
"--no-home",
@@ -200,16 +197,20 @@ func unsquashfsSandboxCmd(unsquashfs string, dest string, filename string, filte
200197
"-B", fmt.Sprintf("%s:%s", tmpdir, rootfsImageDir),
201198
}
202199

200+
if squashfs.ForceUserns {
201+
args = append(args, "--userns")
202+
}
203+
203204
if filename != stdinFile {
204205
filename = filepath.Join(rootfsImageDir, filepath.Base(filename))
205206
}
206207

207208
roFiles := []string{
208-
unsquashfs,
209+
squashfs.UnsquashfsPath,
209210
}
210211

211212
// get the library dependencies of unsquashfs
212-
libs, err := getLibraryBinds(unsquashfs)
213+
libs, err := getLibraryBinds(squashfs.UnsquashfsPath)
213214
if err != nil {
214215
return nil, err
215216
}
@@ -262,7 +263,7 @@ func unsquashfsSandboxCmd(unsquashfs string, dest string, filename string, filte
262263
args = append(args, rootfs)
263264

264265
// unsquashfs execution arguments
265-
args = append(args, unsquashfs)
266+
args = append(args, squashfs.UnsquashfsPath)
266267
args = append(args, opts...)
267268

268269
if overwrite {

internal/pkg/image/unpacker/squashfs_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func TestSquashfs(t *testing.T) {
5050
}
5151

5252
func testSquashfs(t *testing.T, tmpParent string) {
53-
s := NewSquashfs()
53+
s := NewSquashfs(false)
5454

5555
if !s.HasUnsquashfs() {
5656
t.Skip("unsquashfs not found")

internal/pkg/runtime/launcher/native/launcher_linux.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ func (l *Launcher) prepareSquashfs(ctx context.Context, img *imgutil.Image, tryF
10741074
sylog.Warningf("--writable applies to temporary sandbox only, changes will not be written to the original image.")
10751075
}
10761076

1077-
err = extractImage(img, imageDir)
1077+
err = extractImage(img, imageDir, l.cfg.Namespaces.User)
10781078
if err == nil {
10791079
l.engineConfig.SetImage(imageDir)
10801080
l.engineConfig.SetDeleteTempDir(tempDir)
@@ -1167,7 +1167,7 @@ func mkContainerDirs() (tempDir, imageDir string, err error) {
11671167
// extractImage extracts img to directory dir within a temporary directory
11681168
// tempDir. It is the caller's responsibility to remove tempDir
11691169
// when no longer needed.
1170-
func extractImage(img *imgutil.Image, imageDir string) error {
1170+
func extractImage(img *imgutil.Image, imageDir string, userns bool) error {
11711171
sylog.Infof("Converting SIF file to temporary sandbox...")
11721172
unsquashfsPath, err := bin.FindBin("unsquashfs")
11731173
if err != nil {
@@ -1179,7 +1179,7 @@ func extractImage(img *imgutil.Image, imageDir string) error {
11791179
if err != nil {
11801180
return fmt.Errorf("could not extract root filesystem: %s", err)
11811181
}
1182-
s := unpacker.NewSquashfs()
1182+
s := unpacker.NewSquashfs(userns)
11831183
if !s.HasUnsquashfs() && unsquashfsPath != "" {
11841184
s.UnsquashfsPath = unsquashfsPath
11851185
}

pkg/image/image_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func checkPartition(t *testing.T, reader io.Reader) error {
6161
extracted := "/bin/busybox"
6262
dir := t.TempDir()
6363

64-
s := unpacker.NewSquashfs()
64+
s := unpacker.NewSquashfs(false)
6565
if s.HasUnsquashfs() {
6666
if err := s.ExtractFiles([]string{extracted}, reader, dir); err != nil {
6767
return fmt.Errorf("extraction failed: %s", err)

0 commit comments

Comments
 (0)