4
4
5
5
use std:: env:: consts:: ARCH ;
6
6
7
- use anyhow:: Result ;
7
+ use anyhow:: { bail , ensure , Result } ;
8
8
use cap_std:: fs:: Dir ;
9
9
use cap_std_ext:: cap_std;
10
10
use cap_std_ext:: dirext:: CapStdExtDirExt as _;
@@ -15,7 +15,13 @@ use fn_error_context::context;
15
15
/// if it does not exist error.
16
16
#[ context( "Linting" ) ]
17
17
pub ( crate ) fn lint ( root : & Dir ) -> Result < ( ) > {
18
- let lints = [ check_var_run, check_kernel, check_parse_kargs, check_usretc] ;
18
+ let lints = [
19
+ check_var_run,
20
+ check_kernel,
21
+ check_parse_kargs,
22
+ check_usretc,
23
+ check_utf8,
24
+ ] ;
19
25
for lint in lints {
20
26
lint ( & root) ?;
21
27
}
@@ -59,6 +65,33 @@ fn check_kernel(root: &Dir) -> Result<()> {
59
65
Ok ( ( ) )
60
66
}
61
67
68
+ fn check_utf8 ( dir : & Dir ) -> Result < ( ) > {
69
+ for entry in dir. entries ( ) ? {
70
+ let entry = entry?;
71
+ let name = entry. file_name ( ) ;
72
+
73
+ let Some ( strname) = & name. to_str ( ) else {
74
+ // will escape nicely like "abc\xFFdéf"
75
+ bail ! ( "/: Found non-utf8 filename {name:?}" ) ;
76
+ } ;
77
+
78
+ let ifmt = entry. file_type ( ) ?;
79
+ if ifmt. is_symlink ( ) {
80
+ let target = dir. read_link_contents ( & name) ?;
81
+ ensure ! (
82
+ target. to_str( ) . is_some( ) ,
83
+ "/{strname}: Found non-utf8 symlink target"
84
+ ) ;
85
+ } else if ifmt. is_dir ( ) {
86
+ if let Err ( err) = check_utf8 ( & entry. open_dir ( ) ?) {
87
+ // Try to do the path pasting only in the event of an error
88
+ bail ! ( "/{strname}{err:?}" ) ;
89
+ }
90
+ }
91
+ }
92
+ Ok ( ( ) )
93
+ }
94
+
62
95
#[ cfg( test) ]
63
96
fn fixture ( ) -> Result < cap_std_ext:: cap_tempfile:: TempDir > {
64
97
let tempdir = cap_std_ext:: cap_tempfile:: tempdir ( cap_std:: ambient_authority ( ) ) ?;
@@ -117,3 +150,75 @@ fn test_usr_etc() -> Result<()> {
117
150
check_usretc ( root) . unwrap ( ) ;
118
151
Ok ( ( ) )
119
152
}
153
+
154
+ #[ test]
155
+ fn test_non_utf8 ( ) {
156
+ use std:: { ffi:: OsStr , os:: unix:: ffi:: OsStrExt } ;
157
+
158
+ let root = & fixture ( ) . unwrap ( ) ;
159
+
160
+ // Try to create some adversarial symlink situations to ensure the walk doesn't crash
161
+ root. create_dir ( "subdir" ) . unwrap ( ) ;
162
+ // Self-referential symlinks
163
+ root. symlink ( "self" , "self" ) . unwrap ( ) ;
164
+ // Infinitely looping dir symlinks
165
+ root. symlink ( ".." , "subdir/parent" ) . unwrap ( ) ;
166
+ // Broken symlinks
167
+ root. symlink ( "does-not-exist" , "broken" ) . unwrap ( ) ;
168
+ // Out-of-scope symlinks
169
+ root. symlink ( "../../x" , "escape" ) . unwrap ( ) ;
170
+ // Should be fine
171
+ check_utf8 ( root) . unwrap ( ) ;
172
+
173
+ // But this will cause an issue
174
+ let baddir = OsStr :: from_bytes ( b"subdir/2/bad\xff dir" ) ;
175
+ root. create_dir ( "subdir/2" ) . unwrap ( ) ;
176
+ root. create_dir ( baddir) . unwrap ( ) ;
177
+ let Err ( err) = check_utf8 ( root) else {
178
+ unreachable ! ( "Didn't fail" ) ;
179
+ } ;
180
+ assert_eq ! (
181
+ err. to_string( ) ,
182
+ r#"/subdir/2/: Found non-utf8 filename "bad\xFFdir""#
183
+ ) ;
184
+ root. remove_dir ( baddir) . unwrap ( ) ; // Get rid of the problem
185
+ check_utf8 ( root) . unwrap ( ) ; // Check it
186
+
187
+ // Create a new problem in the form of a regular file
188
+ let badfile = OsStr :: from_bytes ( b"regular\xff " ) ;
189
+ root. write ( badfile, b"Hello, world!\n " ) . unwrap ( ) ;
190
+ let Err ( err) = check_utf8 ( root) else {
191
+ unreachable ! ( "Didn't fail" ) ;
192
+ } ;
193
+ assert_eq ! (
194
+ err. to_string( ) ,
195
+ r#"/: Found non-utf8 filename "regular\xFF""#
196
+ ) ;
197
+ root. remove_file ( badfile) . unwrap ( ) ; // Get rid of the problem
198
+ check_utf8 ( root) . unwrap ( ) ; // Check it
199
+
200
+ // And now test invalid symlink targets
201
+ root. symlink ( badfile, "subdir/good-name" ) . unwrap ( ) ;
202
+ let Err ( err) = check_utf8 ( root) else {
203
+ unreachable ! ( "Didn't fail" ) ;
204
+ } ;
205
+ assert_eq ! (
206
+ err. to_string( ) ,
207
+ r#"/subdir/good-name: Found non-utf8 symlink target"#
208
+ ) ;
209
+ root. remove_file ( "subdir/good-name" ) . unwrap ( ) ; // Get rid of the problem
210
+ check_utf8 ( root) . unwrap ( ) ; // Check it
211
+
212
+ // Finally, test a self-referential symlink with an invalid name.
213
+ // We should spot the invalid name before we check the target.
214
+ root. symlink ( badfile, badfile) . unwrap ( ) ;
215
+ let Err ( err) = check_utf8 ( root) else {
216
+ unreachable ! ( "Didn't fail" ) ;
217
+ } ;
218
+ assert_eq ! (
219
+ err. to_string( ) ,
220
+ r#"/: Found non-utf8 filename "regular\xFF""#
221
+ ) ;
222
+ root. remove_file ( badfile) . unwrap ( ) ; // Get rid of the problem
223
+ check_utf8 ( root) . unwrap ( ) ; // Check it
224
+ }
0 commit comments