-
Notifications
You must be signed in to change notification settings - Fork 192
fix: validate volume mount source paths before spawning detached process #3932
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,6 +9,8 @@ import ( | |||||||||||||||||||
| "log/slog" | ||||||||||||||||||||
| "maps" | ||||||||||||||||||||
| "net/url" | ||||||||||||||||||||
| "os" | ||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||
| "slices" | ||||||||||||||||||||
| "strings" | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -1042,6 +1044,22 @@ func (b *runConfigBuilder) processVolumeMounts() error { | |||||||||||||||||||
| return fmt.Errorf("invalid volume format: %s (%w)", volume, err) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Validate source path exists on the host filesystem (CLI context only). | ||||||||||||||||||||
| // Skip for Kubernetes operator context where paths are container-relative, | ||||||||||||||||||||
| // and for resource URIs which are not filesystem paths. | ||||||||||||||||||||
| if b.buildContext != BuildContextOperator && !strings.HasPrefix(source, "resource://") { | ||||||||||||||||||||
| absSource := source | ||||||||||||||||||||
| if !filepath.IsAbs(absSource) { | ||||||||||||||||||||
| absSource, err = filepath.Abs(absSource) | ||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||
| return fmt.Errorf("failed to resolve volume mount source path %q: %w", source, err) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should-fix: When a relative path is used (e.g., Consider showing |
||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+1055
to
+1057
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Must-fix: All The issue itself suggested the correct pattern. Distinguish
Suggested change
Note: this also switches to |
||||||||||||||||||||
| if _, err := os.Stat(absSource); err != nil { | ||||||||||||||||||||
| return fmt.Errorf("volume mount source path does not exist: %s", source) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Check for duplicate mount target | ||||||||||||||||||||
| if existingSource, isDuplicate := existingMounts[target]; isDuplicate { | ||||||||||||||||||||
| slog.Warn("Skipping duplicate mount target", "target", target, "existing_source", existingSource) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -244,6 +244,11 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) { | |
| // Create a mock environment variable validator | ||
| mockValidator := &mockEnvVarValidator{} | ||
|
|
||
| // Create temporary directories for volume mount source paths | ||
| tmpDir1 := t.TempDir() | ||
| tmpDir2 := t.TempDir() | ||
| tmpDir3 := t.TempDir() | ||
|
|
||
| testCases := []struct { | ||
| name string | ||
| builderOptions []RunConfigBuilderOption | ||
|
|
@@ -263,7 +268,7 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) { | |
| { | ||
| name: "Volumes without permission profile but with profile name", | ||
| builderOptions: []RunConfigBuilderOption{ | ||
| WithVolumes([]string{"/host:/container"}), | ||
| WithVolumes([]string{tmpDir1 + ":/container"}), | ||
| WithPermissionProfileNameOrPath(permissions.ProfileNone), | ||
| }, | ||
| expectError: false, | ||
|
|
@@ -273,7 +278,7 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) { | |
| { | ||
| name: "Read-only volume with existing profile", | ||
| builderOptions: []RunConfigBuilderOption{ | ||
| WithVolumes([]string{"/host:/container:ro"}), | ||
| WithVolumes([]string{tmpDir1 + ":/container:ro"}), | ||
| WithPermissionProfile(permissions.BuiltinNoneProfile()), | ||
| }, | ||
| expectError: false, | ||
|
|
@@ -283,7 +288,7 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) { | |
| { | ||
| name: "Read-write volume with existing profile", | ||
| builderOptions: []RunConfigBuilderOption{ | ||
| WithVolumes([]string{"/host:/container"}), | ||
| WithVolumes([]string{tmpDir1 + ":/container"}), | ||
| WithPermissionProfile(permissions.BuiltinNoneProfile()), | ||
| }, | ||
| expectError: false, | ||
|
|
@@ -294,9 +299,9 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) { | |
| name: "Multiple volumes with existing profile", | ||
| builderOptions: []RunConfigBuilderOption{ | ||
| WithVolumes([]string{ | ||
| "/host1:/container1:ro", | ||
| "/host2:/container2", | ||
| "/host3:/container3:ro", | ||
| tmpDir1 + ":/container1:ro", | ||
| tmpDir2 + ":/container2", | ||
| tmpDir3 + ":/container3:ro", | ||
| }), | ||
| WithPermissionProfile(permissions.BuiltinNoneProfile()), | ||
| }, | ||
|
|
@@ -312,6 +317,14 @@ func TestRunConfigBuilder_Build_WithVolumeMounts(t *testing.T) { | |
| }, | ||
| expectError: true, | ||
| }, | ||
| { | ||
| name: "Non-existent source path", | ||
| builderOptions: []RunConfigBuilderOption{ | ||
| WithVolumes([]string{"/nonexistent/path/that/does/not/exist:/container"}), | ||
| WithPermissionProfile(permissions.BuiltinNoneProfile()), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should-fix: A couple of test cases are missing that would strengthen confidence in the guard conditions:
|
||
| }, | ||
| expectError: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range testCases { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must-fix: The
resource://prefix check is incorrect.MountDeclaration.Parse()returnssourceasscheme + "://" + resource— whereschemeis the actual URI scheme (e.g.,volume,tmpfs), not the literal stringresource. So a mount likevolume://mydata:/appreturnssource = "volume://mydata", which does not start withresource://and would incorrectly be stat'd on the filesystem.MountDeclarationalready has anIsResourceURI()method that handles this correctly. Suggested fix:This also makes the code self-documenting — it reads as "skip if this is a resource URI" rather than checking a raw string prefix.