Skip to content

Commit bef4207

Browse files
authored
Merge branch 'master' into fix/tmpdir-sandbox-env
2 parents a92dfce + c84e2b3 commit bef4207

12 files changed

Lines changed: 1132 additions & 115 deletions

File tree

.buildkite/hooks/pre-command

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ if [[ "${BUILDKITE_PIPELINE_INSTALL_RUNTIME:-}" == "true" ]]; then
9595
if [[ -n "${STAGED_BINARIES:-}" ]]; then
9696
# Used `runsc` from STAGED_BINARIES instead of building it from scratch.
9797
export BUILDKITE_STAGED_BINARIES_DIRECTORY="$(mktemp -d)"
98-
gsutil cat "$STAGED_BINARIES" \
98+
gcloud storage cat "$STAGED_BINARIES" \
9999
| tar -C "$BUILDKITE_STAGED_BINARIES_DIRECTORY" -zxvf - runsc
100100
chmod +x "$BUILDKITE_STAGED_BINARIES_DIRECTORY/runsc"
101101
sudo "$BUILDKITE_STAGED_BINARIES_DIRECTORY/runsc" install \

.buildkite/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ steps:
5050
- "make BAZEL_OPTIONS='--config=x86_64 --compilation_mode=opt' artifacts/x86_64"
5151
- "make BAZEL_OPTIONS='--config=aarch64 --compilation_mode=opt' artifacts/aarch64"
5252
- make release RELEASE_NIGHTLY=$$RELEASE_NIGHTLY
53-
- cd repo && gsutil cp -r . gs://gvisor/releases/
53+
- cd repo && gcloud storage cp --recursive . gs://gvisor/releases/
5454
- <<: *common
5555
label: ":ship: Website Deploy"
5656
if: build.branch == "master" && build.tag == null

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ $(RUNTIME_BIN): # See below.
136136
ifeq (,$(STAGED_BINARIES))
137137
@$(call copy,$(RUNSC_TARGET),$(RUNTIME_BIN))
138138
else
139-
gsutil cat "${STAGED_BINARIES}" | \
139+
gcloud storage cat "${STAGED_BINARIES}" | \
140140
tar -C "$(RUNTIME_DIR)" -zxvf - runsc && \
141141
chmod a+rx "$(RUNTIME_BIN)"
142142
endif
@@ -479,7 +479,7 @@ ifeq (,$(STAGED_BINARIES))
479479
sudo mv $$T/containerd-shim-runsc-v1 "$$(dirname $$(which containerd))"; \
480480
rm -rf $$T)
481481
else
482-
gsutil cat "$(STAGED_BINARIES)" | \
482+
gcloud storage cat "$(STAGED_BINARIES)" | \
483483
sudo tar -C "$$(dirname $$(which containerd))" -zxvf - containerd-shim-runsc-v1
484484
endif
485485
@$(call sudo,test/root:root_test,--runtime=$(RUNTIME) -test.v)

images/gpu/triton/Dockerfile.x86_64

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
# Fetches model/tokenizer assets from GCS
33
FROM google/cloud-sdk:541.0.0-slim AS downloader
44
RUN gcloud config set auth/disable_credentials true
5-
RUN gsutil -m cp -r gs://gvisor/tests/models/llama-2-7b-chat-hf /
5+
# Workaround: gcloud storage crashes when copying directly to root ('/').
6+
# We pre-create the directory and use /* to copy contents without nesting.
7+
RUN mkdir -p /llama-2-7b-chat-hf
8+
RUN gcloud storage cp --recursive gs://gvisor/tests/models/llama-2-7b-chat-hf/* /llama-2-7b-chat-hf/
69
RUN mkdir -p /engines
7-
RUN gsutil -m cp -r gs://gvisor/tests/l4/engines/llama-2-7b-chat-hf /engines/
10+
RUN gcloud storage cp --recursive gs://gvisor/tests/l4/engines/llama-2-7b-chat-hf /engines/
811

912
# --- Builder Stage for TensorRT-LLM ---
1013
# This stage uses 'git sparse-checkout' to download *only* the

pkg/sentry/fsimpl/user/path.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,13 @@ func ResolveExecutablePath(ctx context.Context, args *kernel.CreateProcessArgs)
7272
return f, nil
7373
}
7474

75+
// resolve searches for an executable in the given PATH directories.
76+
// Error handling follows glibc execvpe() (posix/execvpe.c).
7577
func resolve(ctx context.Context, creds *auth.Credentials, mns *vfs.MountNamespace, paths []string, name string) (string, error) {
7678
root := mns.Root(ctx)
7779
defer root.DecRef(ctx)
80+
gotEACCES := false
81+
lastErr := error(linuxerr.ENOENT)
7882
for _, p := range paths {
7983
if !path.IsAbs(p) {
8084
// Relative paths aren't safe, no one should be using them.
@@ -94,20 +98,31 @@ func resolve(ctx context.Context, creds *auth.Credentials, mns *vfs.MountNamespa
9498
Flags: linux.O_RDONLY,
9599
}
96100
dentry, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, pop, opts)
97-
if linuxerr.Equals(linuxerr.ENOENT, err) || linuxerr.Equals(linuxerr.EACCES, err) {
98-
// Didn't find it here.
99-
continue
101+
if err == nil {
102+
dentry.DecRef(ctx)
103+
return binPath, nil
100104
}
101-
if err != nil {
105+
106+
// Preserve the last error, matching glibc's errno semantics.
107+
lastErr = err
108+
switch {
109+
case linuxerr.Equals(linuxerr.EACCES, err):
110+
gotEACCES = true
111+
case linuxerr.Equals(linuxerr.ENOENT, err),
112+
linuxerr.Equals(linuxerr.ESTALE, err),
113+
linuxerr.Equals(linuxerr.ENOTDIR, err),
114+
linuxerr.Equals(linuxerr.ENODEV, err),
115+
linuxerr.Equals(linuxerr.ETIMEDOUT, err):
116+
// Skip to next PATH entry.
117+
default:
102118
return "", err
103119
}
104-
dentry.DecRef(ctx)
105-
106-
return binPath, nil
107120
}
108121

109-
// Couldn't find it.
110-
return "", linuxerr.ENOENT
122+
if gotEACCES {
123+
return "", linuxerr.EACCES
124+
}
125+
return "", lastErr
111126
}
112127

113128
// getPath returns the PATH as a slice of strings given the environment

pkg/sentry/fsimpl/user/user_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,158 @@ func TestGetExecUIDGIDFromUser(t *testing.T) {
436436
})
437437
}
438438
}
439+
440+
// newResolveTestVFS creates a tmpfs-backed VFS for resolve() tests.
441+
func newResolveTestVFS(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, *vfs.MountNamespace, vfs.VirtualDentry) {
442+
t.Helper()
443+
ctx := contexttest.Context(t)
444+
creds := auth.CredentialsFromContext(ctx)
445+
446+
vfsObj := &vfs.VirtualFilesystem{}
447+
if err := vfsObj.Init(ctx); err != nil {
448+
t.Fatalf("VFS init: %v", err)
449+
}
450+
vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
451+
AllowUserMount: true,
452+
})
453+
mns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil)
454+
if err != nil {
455+
t.Fatalf("failed to create tmpfs root mount: %v", err)
456+
}
457+
root := mns.Root(ctx)
458+
return ctx, creds, vfsObj, mns, root
459+
}
460+
461+
// createFile creates a regular file at the given path in the test VFS.
462+
func createFile(t *testing.T, ctx context.Context, creds *auth.Credentials, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, filePath string, mode uint16) {
463+
t.Helper()
464+
pop := vfs.PathOperation{
465+
Root: root,
466+
Start: root,
467+
Path: fspath.Parse(filePath),
468+
}
469+
fd, err := vfsObj.OpenAt(ctx, creds, &pop, &vfs.OpenOptions{
470+
Flags: linux.O_CREAT | linux.O_WRONLY,
471+
Mode: linux.S_IFREG | linux.FileMode(mode),
472+
})
473+
if err != nil {
474+
t.Fatalf("failed to create %s: %v", filePath, err)
475+
}
476+
fd.DecRef(ctx)
477+
}
478+
479+
// createDir creates a directory at the given path in the test VFS.
480+
func createDir(t *testing.T, ctx context.Context, creds *auth.Credentials, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, dirPath string) {
481+
t.Helper()
482+
pop := vfs.PathOperation{
483+
Root: root,
484+
Start: root,
485+
Path: fspath.Parse(dirPath),
486+
}
487+
if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{Mode: 0755}); err != nil {
488+
t.Fatalf("failed to create directory %s: %v", dirPath, err)
489+
}
490+
}
491+
492+
// TestResolveExecutablePath tests that resolve() follows glibc execvpe()
493+
// semantics for handling errors during PATH search:
494+
// - ENOENT, ENOTDIR, ESTALE, ENODEV, ETIMEDOUT: skip to next PATH entry.
495+
// - EACCES: record and continue; if no executable is found, return EACCES
496+
// instead of ENOENT.
497+
// - Any other error: stop and return immediately.
498+
//
499+
// Reference: glibc posix/execvpe.c __execvpe_common().
500+
func TestResolveExecutablePath(t *testing.T) {
501+
ctx, creds, vfsObj, mns, root := newResolveTestVFS(t)
502+
defer mns.DecRef(ctx)
503+
defer root.DecRef(ctx)
504+
505+
// Layout:
506+
// /not_a_dir — regular file (triggers ENOTDIR)
507+
// /bin/myexec — executable
508+
createFile(t, ctx, creds, vfsObj, root, "not_a_dir", 0755)
509+
createDir(t, ctx, creds, vfsObj, root, "bin")
510+
createFile(t, ctx, creds, vfsObj, root, "bin/myexec", 0755)
511+
512+
t.Run("SkipENOTDIR", func(t *testing.T) {
513+
// /not_a_dir/myexec → ENOTDIR, should skip and find /bin/myexec.
514+
got, err := resolve(ctx, creds, mns, []string{"/not_a_dir", "/bin"}, "myexec")
515+
if err != nil {
516+
t.Fatalf("resolve failed: %v", err)
517+
}
518+
if got != "/bin/myexec" {
519+
t.Fatalf("expected /bin/myexec, got %v", got)
520+
}
521+
})
522+
523+
t.Run("SkipENOENT", func(t *testing.T) {
524+
// /nonexistent doesn't exist → ENOENT, should skip and find /bin/myexec.
525+
got, err := resolve(ctx, creds, mns, []string{"/nonexistent", "/bin"}, "myexec")
526+
if err != nil {
527+
t.Fatalf("resolve failed: %v", err)
528+
}
529+
if got != "/bin/myexec" {
530+
t.Fatalf("expected /bin/myexec, got %v", got)
531+
}
532+
})
533+
534+
t.Run("AllMissReturnENOENT", func(t *testing.T) {
535+
// PATH has ENOTDIR then ENOENT. glibc preserves the last errno
536+
// from execve(), which is ENOENT from /nonexistent.
537+
_, err := resolve(ctx, creds, mns, []string{"/not_a_dir", "/nonexistent"}, "myexec")
538+
if !linuxerr.Equals(linuxerr.ENOENT, err) {
539+
t.Fatalf("expected ENOENT, got: %v", err)
540+
}
541+
})
542+
543+
t.Run("OnlyENOTDIR", func(t *testing.T) {
544+
// All PATH entries are files (not directories) → ENOTDIR.
545+
// glibc preserves the last errno, which is ENOTDIR.
546+
_, err := resolve(ctx, creds, mns, []string{"/not_a_dir"}, "myexec")
547+
if !linuxerr.Equals(linuxerr.ENOTDIR, err) {
548+
t.Fatalf("expected ENOTDIR, got: %v", err)
549+
}
550+
})
551+
552+
t.Run("EACCESReturnedOverENOENT", func(t *testing.T) {
553+
// Create a file that exists but is not executable (mode 0644).
554+
// The first PATH entry will fail with EACCES, and the second
555+
// with ENOENT. Per glibc, EACCES should be returned.
556+
createDir(t, ctx, creds, vfsObj, root, "noperm")
557+
createFile(t, ctx, creds, vfsObj, root, "noperm/myexec", 0644)
558+
559+
_, err := resolve(ctx, creds, mns, []string{"/noperm", "/nonexistent"}, "myexec")
560+
if !linuxerr.Equals(linuxerr.EACCES, err) {
561+
t.Fatalf("expected EACCES, got: %v", err)
562+
}
563+
})
564+
565+
t.Run("EACCESThenFound", func(t *testing.T) {
566+
// EACCES in first entry, but found in second → success.
567+
got, err := resolve(ctx, creds, mns, []string{"/noperm", "/bin"}, "myexec")
568+
if err != nil {
569+
t.Fatalf("resolve failed: %v", err)
570+
}
571+
if got != "/bin/myexec" {
572+
t.Fatalf("expected /bin/myexec, got %v", got)
573+
}
574+
})
575+
576+
t.Run("EmptyPATH", func(t *testing.T) {
577+
_, err := resolve(ctx, creds, mns, nil, "myexec")
578+
if !linuxerr.Equals(linuxerr.ENOENT, err) {
579+
t.Fatalf("expected ENOENT, got: %v", err)
580+
}
581+
})
582+
583+
t.Run("SkipRelativePath", func(t *testing.T) {
584+
// Relative paths should be skipped.
585+
got, err := resolve(ctx, creds, mns, []string{"relative", "/bin"}, "myexec")
586+
if err != nil {
587+
t.Fatalf("resolve failed: %v", err)
588+
}
589+
if got != "/bin/myexec" {
590+
t.Fatalf("expected /bin/myexec, got %v", got)
591+
}
592+
})
593+
}

pkg/shim/v1/extension/extension.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ import (
2626
// extension should not handle this task request. Returning an error will fail the task request.
2727
var NewExtension func(ctx context.Context, next TaskServiceExt, req *task.CreateTaskRequest) (TaskServiceExt, error)
2828

29+
// NewPodExtension registers an extension constructor which is used when grouping is enabled.
30+
// It may return nil, nil to indicate that the extension should not handle this task request.
31+
// Returning an error will fail the task request.
32+
var NewPodExtension func(ctx context.Context, next TaskServiceExt) (TaskServiceExt, error)
33+
2934
// FSRestoreConfig is the configuration for a FS restore request.
3035
type FSRestoreConfig struct {
3136
ImagePath string

pkg/shim/v1/service.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func New(ctx context.Context, id string, publisher shim.Publisher, cancel func()
6969
s.shimAddress = address
7070
}
7171

72+
s.grouping = getEnableGrouping()
7273
return s, nil
7374
}
7475

@@ -109,6 +110,9 @@ type service struct {
109110
//
110111
// Protected by mu.
111112
ext extension.TaskServiceExt
113+
114+
// grouping indicates if shim grouping is enabled.
115+
grouping bool
112116
}
113117

114118
var _ shim.Shim = (*service)(nil)
@@ -290,22 +294,37 @@ func (s *service) Cleanup(ctx context.Context) (*taskapi.DeleteResponse, error)
290294
func (s *service) Create(ctx context.Context, r *taskapi.CreateTaskRequest) (*taskapi.CreateTaskResponse, error) {
291295
log.L.Debugf("Create, id: %s, bundle: %q", r.ID, r.Bundle)
292296

293-
// Check if we need to create an extension to intercept calls to the container's shim.
294-
if extension.NewExtension != nil {
295-
s.mu.Lock()
296-
var err error
297-
s.ext, err = extension.NewExtension(ctx, s.main, r)
298-
if err != nil {
299-
s.mu.Unlock()
300-
return nil, err
297+
s.mu.Lock()
298+
if s.grouping {
299+
// Create shim extension if required.
300+
if s.ext == nil && extension.NewPodExtension != nil {
301+
log.L.Infof("Create shim extension per pod")
302+
var err error
303+
s.ext, err = extension.NewPodExtension(ctx, s.main)
304+
if err != nil {
305+
log.L.Debugf("Creating shim extension per pod failed with error: %v", err)
306+
s.mu.Unlock()
307+
return nil, err
308+
}
301309
}
302-
if s.ext == nil {
303-
log.L.Debugf("No extension created for container")
304-
} else {
305-
log.L.Infof("Extension created for container")
310+
} else {
311+
// Check if we need to create an extension to intercept calls to the container's shim.
312+
if extension.NewExtension != nil {
313+
log.L.Infof("Create shim extension per container")
314+
var err error
315+
s.ext, err = extension.NewExtension(ctx, s.main, r)
316+
if err != nil {
317+
s.mu.Unlock()
318+
return nil, err
319+
}
306320
}
307-
s.mu.Unlock()
308321
}
322+
if s.ext == nil {
323+
log.L.Debugf("No extension created")
324+
} else {
325+
log.L.Infof("Extension created")
326+
}
327+
s.mu.Unlock()
309328

310329
resp, err := s.get().Create(ctx, r)
311330
return resp, errdefs.ToGRPC(err)

runsc/sandbox/BUILD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,17 @@ go_test(
7272
size = "small",
7373
srcs = [
7474
"memory_test.go",
75+
<<<<<<< fix/tmpdir-sandbox-env
7576
"sandbox_env_test.go",
77+
=======
78+
"network_test.go",
79+
>>>>>>> master
7680
],
7781
library = ":sandbox",
82+
deps = [
83+
"//runsc/boot",
84+
"//runsc/config",
85+
"@com_github_vishvananda_netlink//:go_default_library",
86+
"@org_golang_x_sys//unix:go_default_library",
87+
],
7888
)

0 commit comments

Comments
 (0)