@@ -54,38 +54,38 @@ impl std::fmt::Display for LandlockAvailability {
5454/// This is safe to call from the parent process and does not create any file
5555/// descriptors or modify process state.
5656pub fn probe_availability ( ) -> LandlockAvailability {
57- // landlock_create_ruleset syscall number (same on x86_64 and aarch64).
58- const SYS_LANDLOCK_CREATE_RULESET : libc:: c_long = 444 ;
59- // Flag: return the highest supported ABI version instead of creating a ruleset.
60- const LANDLOCK_CREATE_RULESET_VERSION : libc:: c_uint = 1 << 0 ;
61-
62- // SAFETY: landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)
63- // is a read-only probe that returns the ABI version or an error code.
64- // It does not allocate file descriptors or modify process state.
65- #[ allow( unsafe_code) ]
66- let ret = unsafe {
67- libc:: syscall (
68- SYS_LANDLOCK_CREATE_RULESET ,
69- std:: ptr:: null :: < libc:: c_void > ( ) ,
70- 0_usize ,
71- LANDLOCK_CREATE_RULESET_VERSION ,
72- )
73- } ;
74-
75- if ret >= 0 {
76- #[ allow( clippy:: cast_possible_truncation) ]
77- LandlockAvailability :: Available { abi : ret as i32 }
78- } else {
79- let errno = std:: io:: Error :: last_os_error ( ) . raw_os_error ( ) . unwrap_or ( 0 ) ;
80- match errno {
57+ match crate :: linux:: landlock:: abi_version ( ) {
58+ Ok ( abi) => LandlockAvailability :: Available {
59+ abi : i32:: try_from ( abi) . unwrap_or ( i32:: MAX ) ,
60+ } ,
61+ Err ( error) => match error. raw_os_error ( ) . unwrap_or ( 0 ) {
8162 libc:: ENOSYS => LandlockAvailability :: NotImplemented ,
8263 libc:: EOPNOTSUPP => LandlockAvailability :: NotEnabled ,
8364 libc:: EPERM => LandlockAvailability :: Blocked ,
8465 other => LandlockAvailability :: Unknown ( other) ,
85- }
66+ } ,
8667 }
8768}
8869
70+ /// Lowest Landlock ABI the mandatory baseline accepts (matches [`ABI::V3`]).
71+ /// ABI v3, introduced in Linux 6.2, adds pathname truncation.
72+ const REQUIRED_ABI : i32 = 3 ;
73+
74+ fn check_abi ( availability : & LandlockAvailability ) -> Result < u32 > {
75+ match availability {
76+ LandlockAvailability :: Available { abi } if * abi >= REQUIRED_ABI => Ok ( abi. unsigned_abs ( ) ) ,
77+ _ => Err ( miette:: miette!(
78+ "Landlock ABI v{REQUIRED_ABI} or newer (Linux 6.2+) is required, but Landlock is {availability}"
79+ ) ) ,
80+ }
81+ }
82+
83+ /// Probe the kernel and fail, naming the required and detected ABI, unless it
84+ /// supports the mandatory baseline. Returns the detected ABI.
85+ pub fn require_abi ( ) -> Result < u32 > {
86+ check_abi ( & probe_availability ( ) )
87+ }
88+
8989/// A prepared Landlock ruleset ready to be enforced via `restrict_self()`.
9090///
9191/// Path FDs are opened before enforcement. The capability-free launch path
@@ -141,6 +141,7 @@ pub fn prepare_capability_free_baseline() -> Result<PreparedRuleset> {
141141fn prepare_capability_free_baseline_at ( root : & Path ) -> Result < PreparedRuleset > {
142142 // Unlike optional filesystem policy, self-protection must cover pathname
143143 // truncation as well as opens. Never silently downgrade this ABI requirement.
144+ require_abi ( ) ?;
144145 let abi = ABI :: V3 ;
145146 let access = AccessFs :: from_all ( abi) ;
146147 let mut ruleset = Ruleset :: default ( )
@@ -922,4 +923,35 @@ mod tests {
922923 "probe_availability Debug should produce output"
923924 ) ;
924925 }
926+
927+ #[ test]
928+ fn check_abi_accepts_the_required_abi_or_newer ( ) {
929+ for abi in [ REQUIRED_ABI , REQUIRED_ABI + 4 ] {
930+ let detected = check_abi ( & LandlockAvailability :: Available { abi } ) . unwrap ( ) ;
931+ assert_eq ! ( detected, abi. unsigned_abs( ) ) ;
932+ }
933+ }
934+
935+ #[ test]
936+ fn check_abi_names_the_required_and_detected_abi ( ) {
937+ let err = check_abi ( & LandlockAvailability :: Available { abi : 2 } )
938+ . unwrap_err ( )
939+ . to_string ( ) ;
940+ assert ! ( err. contains( "ABI v3 or newer" ) , "{err}" ) ;
941+ assert ! ( err. contains( "ABI v2" ) , "{err}" ) ;
942+ }
943+
944+ #[ test]
945+ fn check_abi_rejects_unavailable_landlock ( ) {
946+ for availability in [
947+ LandlockAvailability :: NotImplemented ,
948+ LandlockAvailability :: NotEnabled ,
949+ LandlockAvailability :: Blocked ,
950+ LandlockAvailability :: Unknown ( libc:: EIO ) ,
951+ ] {
952+ let err = check_abi ( & availability) . unwrap_err ( ) . to_string ( ) ;
953+ assert ! ( err. contains( "ABI v3 or newer" ) , "{err}" ) ;
954+ assert ! ( err. contains( & availability. to_string( ) ) , "{err}" ) ;
955+ }
956+ }
925957}
0 commit comments