@@ -74,7 +74,7 @@ unsafe fn nt_open_file(
74
74
/// `options` will be OR'd with `FILE_OPEN_REPARSE_POINT`.
75
75
fn open_link_no_reparse (
76
76
parent : & File ,
77
- path : & [ u16 ] ,
77
+ path : & UnicodeStringNZ ,
78
78
access : u32 ,
79
79
options : u32 ,
80
80
) -> Result < Option < File > , WinError > {
@@ -90,7 +90,7 @@ fn open_link_no_reparse(
90
90
static ATTRIBUTES : Atomic < u32 > = AtomicU32 :: new ( c:: OBJ_DONT_REPARSE ) ;
91
91
92
92
let result = unsafe {
93
- let mut path_str = c :: UNICODE_STRING :: from_ref ( path) ;
93
+ let mut path_str = path. as_unicode_string ( ) ;
94
94
let mut object = c:: OBJECT_ATTRIBUTES {
95
95
ObjectName : & mut path_str,
96
96
RootDirectory : parent. as_raw_handle ( ) ,
@@ -129,7 +129,7 @@ fn open_link_no_reparse(
129
129
}
130
130
}
131
131
132
- fn open_dir ( parent : & File , name : & [ u16 ] ) -> Result < Option < File > , WinError > {
132
+ fn open_dir ( parent : & File , name : & UnicodeStringNZ ) -> Result < Option < File > , WinError > {
133
133
// Open the directory for synchronous directory listing.
134
134
open_link_no_reparse (
135
135
parent,
@@ -140,7 +140,7 @@ fn open_dir(parent: &File, name: &[u16]) -> Result<Option<File>, WinError> {
140
140
)
141
141
}
142
142
143
- fn delete ( parent : & File , name : & [ u16 ] ) -> Result < ( ) , WinError > {
143
+ fn delete ( parent : & File , name : & UnicodeStringNZ ) -> Result < ( ) , WinError > {
144
144
// Note that the `delete` function consumes the opened file to ensure it's
145
145
// dropped immediately. See module comments for why this is important.
146
146
match open_link_no_reparse ( parent, name, c:: DELETE , 0 ) {
@@ -171,14 +171,49 @@ fn retry<T: PartialEq>(
171
171
}
172
172
}
173
173
174
+ /// A UNICODE_STRING that's guarenteed to have null after the end of the string.
175
+ ///
176
+ /// Mitigation for #143078.
177
+ /// It does not prevent interior nulls but ensures any software intercepting Windows API calls
178
+ /// and assuming null-termination will at least not have a memory safety issue.
179
+ struct UnicodeStringNZ {
180
+ s : Vec < u16 > ,
181
+ }
182
+ impl UnicodeStringNZ {
183
+ pub fn new ( ) -> Self {
184
+ // This should be large enough to prevent reallocation because filenames
185
+ // are by convention limited to 255 u16s.
186
+ let mut s = Vec :: with_capacity ( 256 ) ;
187
+ s. push ( 0 ) ;
188
+ Self { s }
189
+ }
190
+
191
+ pub fn as_unicode_string ( & self ) -> c:: UNICODE_STRING {
192
+ let mut u = c:: UNICODE_STRING :: from_ref ( & self . s ) ;
193
+ // truncate the length to before the null
194
+ // note: the length is in bytes and is not a count of u16s.
195
+ u. Length -= 2 ;
196
+ u
197
+ }
198
+
199
+ pub fn set ( & mut self , s : & [ u16 ] ) -> & Self {
200
+ self . s . truncate ( 0 ) ;
201
+ self . s . extend_from_slice ( s) ;
202
+ self . s . push ( 0 ) ;
203
+ self
204
+ }
205
+ }
206
+
174
207
pub fn remove_dir_all_iterative ( dir : File ) -> Result < ( ) , WinError > {
175
208
let mut buffer = DirBuff :: new ( ) ;
176
209
let mut dirlist = vec ! [ dir] ;
210
+ let mut string_buffer = UnicodeStringNZ :: new ( ) ;
177
211
178
212
let mut restart = true ;
179
213
' outer: while let Some ( dir) = dirlist. pop ( ) {
180
214
let more_data = dir. fill_dir_buff ( & mut buffer, restart) ?;
181
215
for ( name, is_directory) in buffer. iter ( ) {
216
+ let name = string_buffer. set ( & name) ;
182
217
if is_directory {
183
218
let Some ( subdir) = open_dir ( & dir, & name) ? else { continue } ;
184
219
dirlist. push ( dir) ;
@@ -197,7 +232,8 @@ pub fn remove_dir_all_iterative(dir: File) -> Result<(), WinError> {
197
232
} else {
198
233
// Attempt to delete, retrying on not empty errors because we may
199
234
// need to wait some time for files to be removed from the filesystem.
200
- retry ( || delete ( & dir, & [ ] ) , WinError :: DIR_NOT_EMPTY ) ?;
235
+ let name = string_buffer. set ( & [ ] ) ;
236
+ retry ( || delete ( & dir, name) , WinError :: DIR_NOT_EMPTY ) ?;
201
237
restart = true ;
202
238
}
203
239
}
0 commit comments