33
44//! Shared validation helpers for driver-config mounts.
55
6- use std:: collections:: HashSet ;
76use std:: path:: Path ;
87
98/// `SELinux` relabelling mode for bind mounts.
@@ -92,6 +91,19 @@ pub fn validate_container_mount_target(target: &str) -> Result<(), String> {
9291 if !target. starts_with ( '/' ) {
9392 return Err ( "mount target must be an absolute container path" . to_string ( ) ) ;
9493 }
94+ if target != "/" {
95+ let segments = target. split ( '/' ) . skip ( 1 ) . collect :: < Vec < _ > > ( ) ;
96+ let has_internal_empty_segment = segments
97+ . iter ( )
98+ . take ( segments. len ( ) . saturating_sub ( 1 ) )
99+ . any ( |segment| segment. is_empty ( ) ) ;
100+ if has_internal_empty_segment || segments. contains ( & "." ) {
101+ return Err (
102+ "mount target must be normalized and must not contain empty path segments or '.'"
103+ . to_string ( ) ,
104+ ) ;
105+ }
106+ }
95107 let path = Path :: new ( target) ;
96108 if path == Path :: new ( "/" ) {
97109 return Err ( "mount target must not be the container root" . to_string ( ) ) ;
@@ -128,22 +140,6 @@ pub fn path_is_or_under(path: &Path, parent: &Path) -> bool {
128140 path == parent || path. starts_with ( parent)
129141}
130142
131- /// Validate that already-normalized driver mount targets are unique.
132- pub fn validate_unique_mount_targets < ' a > (
133- targets : impl IntoIterator < Item = & ' a str > ,
134- driver_name : & str ,
135- ) -> Result < ( ) , String > {
136- let mut seen = HashSet :: new ( ) ;
137- for target in targets {
138- if !seen. insert ( target) {
139- return Err ( format ! (
140- "duplicate {driver_name} driver_config mount target '{target}'"
141- ) ) ;
142- }
143- }
144- Ok ( ( ) )
145- }
146-
147143#[ cfg( test) ]
148144mod tests {
149145 use super :: * ;
@@ -180,33 +176,6 @@ mod tests {
180176 validate_container_mount_target ( "/etc/openshell-tools" ) . unwrap ( ) ;
181177 }
182178
183- #[ test]
184- fn path_is_or_under_matches_boundaries ( ) {
185- assert ! ( path_is_or_under(
186- Path :: new( "/sandbox" ) ,
187- Path :: new( "/sandbox" )
188- ) ) ;
189- assert ! ( path_is_or_under(
190- Path :: new( "/sandbox/work" ) ,
191- Path :: new( "/sandbox" )
192- ) ) ;
193- assert ! ( !path_is_or_under(
194- Path :: new( "/sandbox-work" ) ,
195- Path :: new( "/sandbox" )
196- ) ) ;
197- }
198-
199- #[ test]
200- fn unique_mount_targets_rejects_duplicates ( ) {
201- let err =
202- validate_unique_mount_targets ( [ "/sandbox/work" , "/sandbox/work" ] , "test" ) . unwrap_err ( ) ;
203-
204- assert_eq ! (
205- err,
206- "duplicate test driver_config mount target '/sandbox/work'"
207- ) ;
208- }
209-
210179 #[ test]
211180 fn mount_subpath_must_be_relative_without_parent_dirs ( ) {
212181 assert ! ( validate_mount_subpath( "project/a" ) . is_ok( ) ) ;
@@ -230,4 +199,20 @@ mod tests {
230199 "mount target must not contain surrounding whitespace"
231200 ) ;
232201 }
202+ #[ test]
203+ fn mount_target_rejects_internal_empty_or_dot_segments ( ) {
204+ assert_eq ! (
205+ validate_container_mount_target( "/sandbox/work//tmp" ) . unwrap_err( ) ,
206+ "mount target must be normalized and must not contain empty path segments or '.'"
207+ ) ;
208+ assert_eq ! (
209+ validate_container_mount_target( "/sandbox/work/./tmp" ) . unwrap_err( ) ,
210+ "mount target must be normalized and must not contain empty path segments or '.'"
211+ ) ;
212+ assert_eq ! (
213+ validate_container_mount_target( "/sandbox/work/../../tmp" ) . unwrap_err( ) ,
214+ "mount target must not contain '..'"
215+ ) ;
216+ validate_container_mount_target ( "/sandbox/work/" ) . unwrap ( ) ;
217+ }
233218}
0 commit comments