@@ -8,6 +8,7 @@ use camino::Utf8PathBuf;
8
8
use flate2:: write:: GzEncoder ;
9
9
use flate2:: Compression ;
10
10
use std:: collections:: BTreeSet ;
11
+ use std:: os:: unix:: fs:: MetadataExt ;
11
12
use std:: os:: unix:: fs:: PermissionsExt ;
12
13
use std:: process:: ExitStatus ;
13
14
use std:: time:: Duration ;
@@ -17,6 +18,7 @@ use tedge_config::models::AbsolutePath;
17
18
use tedge_config:: TEdgeConfig ;
18
19
use tedge_utils:: file;
19
20
use tracing:: debug;
21
+ use uzers;
20
22
21
23
#[ derive( Debug ) ]
22
24
pub struct DiagCollectCommand {
@@ -139,13 +141,9 @@ impl DiagCollectCommand {
139
141
140
142
while let Some ( entry) = entries. next_entry ( ) . await ? {
141
143
if let Ok ( path) = Utf8PathBuf :: from_path_buf ( entry. path ( ) ) {
142
- if path. extension ( ) == Some ( "ignore" ) {
143
- debug ! ( "Skipping ignored file: {:?}" , entry. path( ) ) ;
144
- } else if path. is_file ( ) && is_executable ( & path) . await {
144
+ if validate_plugin ( & path, logger) . await {
145
145
plugins. insert ( path) ;
146
146
continue ;
147
- } else {
148
- logger. warning ( & format ! ( "Skipping non-executable file: {:?}" , entry. path( ) ) ) ;
149
147
}
150
148
} else {
151
149
logger. warning ( & format ! ( "Ignoring invalid path: {:?}" , entry. path( ) ) ) ;
@@ -160,7 +158,7 @@ impl DiagCollectCommand {
160
158
) -> Result < ExitStatus , anyhow:: Error > {
161
159
let plugin_name = plugin_path
162
160
. file_stem ( )
163
- . with_context ( || format ! ( "No file name for {}" , plugin_path ) ) ?;
161
+ . with_context ( || format ! ( "No file name for {plugin_path}" ) ) ?;
164
162
let plugin_output_dir = self . diag_dir . join ( plugin_name) ;
165
163
let output_file = plugin_output_dir. join ( "output.log" ) ;
166
164
file:: create_directory_with_defaults ( & plugin_output_dir)
@@ -206,9 +204,40 @@ impl DiagCollectCommand {
206
204
}
207
205
}
208
206
209
- async fn is_executable ( path : & Utf8Path ) -> bool {
207
+ async fn validate_plugin ( path : & Utf8Path , logger : & mut DualLogger ) -> bool {
208
+ if path. extension ( ) == Some ( "ignore" ) {
209
+ debug ! ( "Skipping file: {path} (ignored file)" ) ;
210
+ return false ;
211
+ }
212
+
213
+ if !path. is_file ( ) {
214
+ debug ! ( "Skipping file: {path} (not a regular file)" ) ;
215
+ return false ;
216
+ }
217
+
210
218
match tokio:: fs:: metadata ( path) . await {
211
- Ok ( metadata) => metadata. permissions ( ) . mode ( ) & 0o111 != 0 ,
219
+ Ok ( metadata) => {
220
+ if metadata. permissions ( ) . mode ( ) & 0o111 == 0 {
221
+ logger. warning ( & format ! ( "Skipping file: {path} (not executable)" ) ) ;
222
+ return false ;
223
+ }
224
+
225
+ // extra metadata check when the process is running by the root user for the security
226
+ if uzers:: get_current_uid ( ) == 0 {
227
+ if metadata. uid ( ) != 0 {
228
+ logger. warning ( & format ! ( "Skipping file: {path} (not owned by root)" ) ) ;
229
+ return false ;
230
+ }
231
+ if metadata. permissions ( ) . mode ( ) & 0o022 != 0 {
232
+ logger. warning ( & format ! (
233
+ "Skipping file: {path} (writable by non-root users)" ,
234
+ ) ) ;
235
+ return false ;
236
+ }
237
+ }
238
+
239
+ true
240
+ }
212
241
Err ( _) => false ,
213
242
}
214
243
}
0 commit comments