11use std:: {
2- fs,
2+ env , fs,
33 io:: { Cursor , Read } ,
4- path:: Path ,
4+ path:: { Path , PathBuf } ,
55} ;
66
77use flate2:: read:: GzDecoder ;
@@ -17,18 +17,28 @@ pub struct KernelCapability {
1717
1818impl KernelCapability {
1919 pub fn detect ( ) -> Self {
20- let lsm_text = fs:: read_to_string ( "/sys/kernel/security/lsm" ) . unwrap_or_default ( ) ;
21- let config_text = read_kernel_config ( ) . unwrap_or_default ( ) ;
20+ let proc_root = path_from_env ( "PROCFS_ROOT" , "/proc" ) ;
21+ let sys_root = path_from_env ( "SYSFS_ROOT" , "/sys" ) ;
22+ let boot_root =
23+ host_sibling_root ( & proc_root, "boot" ) . unwrap_or_else ( || PathBuf :: from ( "/boot" ) ) ;
24+
25+ Self :: detect_from_roots ( & proc_root, & sys_root, & boot_root)
26+ }
27+
28+ pub fn detect_from_roots ( proc_root : & Path , sys_root : & Path , boot_root : & Path ) -> Self {
29+ let lsm_text = fs:: read_to_string ( sys_root. join ( "kernel/security/lsm" ) ) . unwrap_or_default ( ) ;
30+ let config_text = read_kernel_config_from_roots ( proc_root, boot_root) . unwrap_or_default ( ) ;
31+ let bpf_lsm_active = lsm_has_bpf ( & lsm_text) ;
2232
2333 Self {
24- bpf_lsm_configured : config_enabled ( & config_text, "CONFIG_BPF_LSM" ) ,
25- bpf_lsm_active : lsm_has_bpf ( & lsm_text ) ,
34+ bpf_lsm_configured : config_enabled ( & config_text, "CONFIG_BPF_LSM" ) || bpf_lsm_active ,
35+ bpf_lsm_active,
2636 bpf_kprobe_override_configured : config_enabled (
2737 & config_text,
2838 "CONFIG_BPF_KPROBE_OVERRIDE" ,
2939 ) ,
3040 seccomp_filter_configured : config_enabled ( & config_text, "CONFIG_SECCOMP_FILTER" ) ,
31- btf_vmlinux_available : Path :: new ( "/sys/ kernel/btf/vmlinux") . exists ( ) ,
41+ btf_vmlinux_available : sys_root . join ( " kernel/btf/vmlinux") . exists ( ) ,
3242 }
3343 }
3444
@@ -37,6 +47,18 @@ impl KernelCapability {
3747 }
3848}
3949
50+ fn path_from_env ( name : & str , default : & str ) -> PathBuf {
51+ env:: var_os ( name)
52+ . filter ( |value| !value. is_empty ( ) )
53+ . map ( PathBuf :: from)
54+ . unwrap_or_else ( || PathBuf :: from ( default) )
55+ }
56+
57+ fn host_sibling_root ( proc_root : & Path , sibling : & str ) -> Option < PathBuf > {
58+ let parent = proc_root. parent ( ) ?;
59+ Some ( parent. join ( sibling) )
60+ }
61+
4062fn lsm_has_bpf ( lsm_text : & str ) -> bool {
4163 lsm_text
4264 . trim ( )
@@ -54,20 +76,24 @@ fn config_enabled(config_text: &str, option: &str) -> bool {
5476}
5577
5678fn read_kernel_config ( ) -> Option < String > {
57- if let Some ( config) = read_boot_kernel_config ( ) {
79+ read_kernel_config_from_roots ( Path :: new ( "/proc" ) , Path :: new ( "/boot" ) )
80+ }
81+
82+ fn read_kernel_config_from_roots ( proc_root : & Path , boot_root : & Path ) -> Option < String > {
83+ if let Some ( config) = read_boot_kernel_config ( proc_root, boot_root) {
5884 return Some ( config) ;
5985 }
60- read_proc_kernel_config ( )
86+ read_proc_kernel_config ( proc_root )
6187}
6288
63- fn read_boot_kernel_config ( ) -> Option < String > {
64- let release = fs:: read_to_string ( "/proc/ sys/kernel/osrelease") . ok ( ) ?;
65- let path = format ! ( "/boot/ config-{}" , release. trim( ) ) ;
89+ fn read_boot_kernel_config ( proc_root : & Path , boot_root : & Path ) -> Option < String > {
90+ let release = fs:: read_to_string ( proc_root . join ( " sys/kernel/osrelease") ) . ok ( ) ?;
91+ let path = boot_root . join ( format ! ( "config-{}" , release. trim( ) ) ) ;
6692 fs:: read_to_string ( path) . ok ( )
6793}
6894
69- fn read_proc_kernel_config ( ) -> Option < String > {
70- let compressed = fs:: read ( "/proc/ config.gz") . ok ( ) ?;
95+ fn read_proc_kernel_config ( proc_root : & Path ) -> Option < String > {
96+ let compressed = fs:: read ( proc_root . join ( " config.gz") ) . ok ( ) ?;
7197 decode_gzip ( & compressed) . ok ( )
7298}
7399
@@ -113,4 +139,40 @@ mod tests {
113139 }
114140 . supports_exec_lsm_enforcement( ) ) ;
115141 }
142+
143+ #[ test]
144+ fn detect_from_roots_reads_host_sysfs_lsm_in_container ( ) {
145+ let root = make_temp_root ( "host-sysfs-lsm" ) ;
146+ let proc_root = root. join ( "host-proc" ) ;
147+ let sys_root = root. join ( "host-sys" ) ;
148+ let boot_root = root. join ( "boot" ) ;
149+ fs:: create_dir_all ( sys_root. join ( "kernel/security" ) ) . unwrap ( ) ;
150+ fs:: create_dir_all ( sys_root. join ( "kernel/btf" ) ) . unwrap ( ) ;
151+ fs:: create_dir_all ( proc_root. join ( "sys/kernel" ) ) . unwrap ( ) ;
152+ fs:: write (
153+ sys_root. join ( "kernel/security/lsm" ) ,
154+ "capability,yama,selinux,bpf" ,
155+ )
156+ . unwrap ( ) ;
157+ fs:: write ( sys_root. join ( "kernel/btf/vmlinux" ) , b"btf" ) . unwrap ( ) ;
158+ fs:: write ( proc_root. join ( "sys/kernel/osrelease" ) , "4.18.0-test\n " ) . unwrap ( ) ;
159+
160+ let capability = KernelCapability :: detect_from_roots ( & proc_root, & sys_root, & boot_root) ;
161+
162+ assert ! ( capability. bpf_lsm_active) ;
163+ assert ! ( capability. bpf_lsm_configured) ;
164+ assert ! ( capability. btf_vmlinux_available) ;
165+
166+ let _ = fs:: remove_dir_all ( root) ;
167+ }
168+
169+ fn make_temp_root ( name : & str ) -> std:: path:: PathBuf {
170+ let root = std:: env:: temp_dir ( ) . join ( format ! (
171+ "deepflow-kernel-capability-{name}-{}" ,
172+ std:: process:: id( )
173+ ) ) ;
174+ let _ = fs:: remove_dir_all ( & root) ;
175+ fs:: create_dir_all ( & root) . unwrap ( ) ;
176+ root
177+ }
116178}
0 commit comments