11// SPDX-License-Identifier: Apache-2.0 OR MIT
22
33use crate :: { uapi, Access , CompatError } ;
4+ use std:: fmt:: { self , Display , Formatter } ;
5+ use std:: io:: Error ;
46
57#[ cfg( test) ]
68use std:: convert:: TryInto ;
@@ -70,20 +72,10 @@ pub enum ABI {
7072 V6 = 6 ,
7173}
7274
75+ // ABI should not be dynamically created (in other crates) according to the running kernel
76+ // to avoid inconsistent behaviors and non-determinism. Creating ABIs based on runtime detection
77+ // can lead to unreliable sandboxing where rules might differ between executions.
7378impl ABI {
74- // Must remain private to avoid inconsistent behavior by passing Ok(self) to a builder method,
75- // e.g. to make it impossible to call ruleset.handle_fs(ABI::new_current()?)
76- fn new_current ( ) -> Self {
77- ABI :: from ( unsafe {
78- // Landlock ABI version starts at 1 but errno is only set for negative values.
79- uapi:: landlock_create_ruleset (
80- std:: ptr:: null ( ) ,
81- 0 ,
82- uapi:: LANDLOCK_CREATE_RULESET_VERSION ,
83- )
84- } )
85- }
86-
8779 #[ cfg( test) ]
8880 fn is_known ( value : i32 ) -> bool {
8981 value > 0 && value < ABI :: COUNT as i32
@@ -96,8 +88,6 @@ impl ABI {
9688impl From < i32 > for ABI {
9789 fn from ( value : i32 ) -> ABI {
9890 match value {
99- // The only possible error values should be EOPNOTSUPP and ENOSYS, but let's interpret
100- // all kind of errors as unsupported.
10191 n if n <= 0 => ABI :: Unsupported ,
10292 1 => ABI :: V1 ,
10393 2 => ABI :: V2 ,
@@ -126,14 +116,14 @@ fn abi_from() {
126116 }
127117
128118 assert_eq ! ( ABI :: from( last_i + 1 ) , last_abi) ;
129- assert_eq ! ( ABI :: from( 9 ) , last_abi) ;
119+ assert_eq ! ( ABI :: from( 999 ) , last_abi) ;
130120}
131121
132122#[ test]
133123fn known_abi ( ) {
134124 assert ! ( !ABI :: is_known( -1 ) ) ;
135125 assert ! ( !ABI :: is_known( 0 ) ) ;
136- assert ! ( !ABI :: is_known( 99 ) ) ;
126+ assert ! ( !ABI :: is_known( 999 ) ) ;
137127
138128 let mut last_i = -1 ;
139129 for ( i, _) in ABI :: iter ( ) . enumerate ( ) . skip ( 1 ) {
@@ -143,6 +133,108 @@ fn known_abi() {
143133 assert ! ( !ABI :: is_known( last_i + 1 ) ) ;
144134}
145135
136+ impl Display for ABI {
137+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
138+ match self {
139+ ABI :: Unsupported => write ! ( f, "unsupported" ) ,
140+ v => ( * v as u32 ) . fmt ( f) ,
141+ }
142+ }
143+ }
144+
145+ /// Status of Landlock support for the running system.
146+ ///
147+ /// This enum is used to represent the status of the Landlock support for the system where the code
148+ /// is executed. It can indicate whether Landlock is available or not.
149+ ///
150+ /// # Warning
151+ ///
152+ /// Sandboxed programs should only use this data to log or provide information to users,
153+ /// not to change their behavior according to this status. Indeed, the `Ruleset` and the other
154+ /// types are designed to handle the compatibility in a simple and safe way.
155+ #[ derive( Copy , Clone , Debug , PartialEq , Eq ) ]
156+ pub enum LandlockStatus {
157+ /// Landlock is supported but not enabled (`EOPNOTSUPP`).
158+ NotEnabled ,
159+ /// Landlock is not implemented (i.e. not built into the running kernel: `ENOSYS`).
160+ NotImplemented ,
161+ /// Landlock is available and supported up to the given ABI.
162+ ///
163+ /// `Option<i32>` contains the raw ABI value if it's greater than the greatest known ABI,
164+ /// which would mean that the running kernel is newer than the Landlock crate.
165+ Available ( ABI , Option < i32 > ) ,
166+ }
167+
168+ impl LandlockStatus {
169+ // Must remain private to avoid inconsistent behavior using such unknown-at-build-time ABI
170+ // e.g., AccessFs::from_all(ABI::new_current())
171+ //
172+ // This should not be Default::default() because the returned value would may not be the same
173+ // for all users.
174+ fn current ( ) -> Self {
175+ // Landlock ABI version starts at 1 but errno is only set for negative values.
176+ let v = unsafe {
177+ uapi:: landlock_create_ruleset (
178+ std:: ptr:: null ( ) ,
179+ 0 ,
180+ uapi:: LANDLOCK_CREATE_RULESET_VERSION ,
181+ )
182+ } ;
183+ if v < 0 {
184+ // The only possible error values should be EOPNOTSUPP and ENOSYS.
185+ match Error :: last_os_error ( ) . raw_os_error ( ) {
186+ Some ( libc:: EOPNOTSUPP ) => Self :: NotEnabled ,
187+ _ => Self :: NotImplemented ,
188+ }
189+ } else {
190+ let abi = ABI :: from ( v) ;
191+ Self :: Available ( abi, ( v != abi as i32 ) . then_some ( v) )
192+ }
193+ }
194+ }
195+
196+ // Test against the running kernel.
197+ #[ test]
198+ fn test_current_landlock_status ( ) {
199+ let status = LandlockStatus :: current ( ) ;
200+ if ABI :: from ( * TEST_ABI ) == ABI :: Unsupported {
201+ assert_eq ! ( status, LandlockStatus :: NotImplemented ) ;
202+ } else {
203+ assert ! ( matches!( status, LandlockStatus :: Available ( abi, _) if abi == ( * TEST_ABI ) . into( ) ) ) ;
204+ if std:: env:: var ( TEST_ABI_ENV_NAME ) . is_ok ( ) {
205+ // We cannot reliably check for unknown kernel.
206+ assert ! ( matches!( status, LandlockStatus :: Available ( _, None ) ) ) ;
207+ }
208+ }
209+ }
210+
211+ impl From < LandlockStatus > for ABI {
212+ fn from ( status : LandlockStatus ) -> Self {
213+ match status {
214+ // The only possible error values should be EOPNOTSUPP and ENOSYS,
215+ // but let's convert all kind of errors as unsupported.
216+ LandlockStatus :: NotEnabled | LandlockStatus :: NotImplemented => ABI :: Unsupported ,
217+ LandlockStatus :: Available ( abi, _) => abi,
218+ }
219+ }
220+ }
221+
222+ // This is only useful to tests and should not be exposed publicly because
223+ // the mapping can only be partial.
224+ #[ cfg( test) ]
225+ impl From < ABI > for LandlockStatus {
226+ fn from ( abi : ABI ) -> Self {
227+ match abi {
228+ // Convert to ENOSYS because of check_ruleset_support() and ruleset_unsupported() tests.
229+ ABI :: Unsupported => Self :: NotImplemented ,
230+ _ => Self :: Available ( abi, None ) ,
231+ }
232+ }
233+ }
234+
235+ #[ cfg( test) ]
236+ static TEST_ABI_ENV_NAME : & str = "LANDLOCK_CRATE_TEST_ABI" ;
237+
146238#[ cfg( test) ]
147239lazy_static ! {
148240 static ref TEST_ABI : ABI = match std:: env:: var( "LANDLOCK_CRATE_TEST_ABI" ) {
@@ -154,7 +246,7 @@ lazy_static! {
154246 panic!( "Unknown ABI: {n}" ) ;
155247 }
156248 }
157- Err ( std:: env:: VarError :: NotPresent ) => ABI :: new_current ( ) ,
249+ Err ( std:: env:: VarError :: NotPresent ) => LandlockStatus :: current ( ) . into ( ) ,
158250 Err ( e) => panic!( "Failed to read LANDLOCK_CRATE_TEST_ABI: {e}" ) ,
159251 } ;
160252}
@@ -172,17 +264,21 @@ pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: Option<
172264
173265#[ cfg( test) ]
174266pub ( crate ) fn get_errno_from_landlock_status ( ) -> Option < i32 > {
175- use std:: io:: Error ;
176-
177- match ABI :: new_current ( ) {
178- ABI :: Unsupported => match Error :: last_os_error ( ) . raw_os_error ( ) {
179- // Returns ENOSYS when the kernel is not built with Landlock support,
180- // or EOPNOTSUPP when Landlock is supported but disabled at boot time.
181- ret @ Some ( libc:: ENOSYS | libc:: EOPNOTSUPP ) => ret,
182- // Other values can only come from bogus seccomp filters or debug tampering.
183- _ => unreachable ! ( ) ,
184- } ,
185- _ => None ,
267+ match LandlockStatus :: current ( ) {
268+ LandlockStatus :: NotImplemented | LandlockStatus :: NotEnabled => {
269+ match Error :: last_os_error ( ) . raw_os_error ( ) {
270+ // Returns ENOSYS when the kernel is not built with Landlock support,
271+ // or EOPNOTSUPP when Landlock is supported but disabled at boot time.
272+ ret @ Some ( libc:: ENOSYS | libc:: EOPNOTSUPP ) => ret,
273+ // Other values can only come from bogus seccomp filters or debugging tampering.
274+ ret => {
275+ eprintln ! ( "Current kernel should support this Landlock ABI according to $LANDLOCK_CRATE_TEST_ABI" ) ;
276+ eprintln ! ( "Unexpected result: {ret:?}" ) ;
277+ unreachable ! ( ) ;
278+ }
279+ }
280+ }
281+ LandlockStatus :: Available ( _, _) => None ,
186282 }
187283}
188284
@@ -194,7 +290,7 @@ fn current_kernel_abi() {
194290 // Landlock ABI version known by this crate is automatically set.
195291 // From Linux 5.13 to 5.18, you need to run: LANDLOCK_CRATE_TEST_ABI=1 cargo test
196292 let test_abi = * TEST_ABI ;
197- let current_abi = ABI :: new_current ( ) ;
293+ let current_abi = LandlockStatus :: current ( ) . into ( ) ;
198294 println ! (
199295 "Current kernel version: {}" ,
200296 std:: fs:: read_to_string( "/proc/version" )
@@ -277,34 +373,45 @@ fn compat_state_update_2() {
277373#[ cfg_attr( test, derive( PartialEq ) ) ]
278374#[ derive( Copy , Clone , Debug ) ]
279375pub ( crate ) struct Compatibility {
280- abi : ABI ,
376+ status : LandlockStatus ,
281377 pub ( crate ) level : Option < CompatLevel > ,
282378 pub ( crate ) state : CompatState ,
283379}
284380
285- impl From < ABI > for Compatibility {
286- fn from ( abi : ABI ) -> Self {
381+ impl From < LandlockStatus > for Compatibility {
382+ fn from ( status : LandlockStatus ) -> Self {
287383 Compatibility {
288- abi ,
384+ status ,
289385 level : Default :: default ( ) ,
290386 state : CompatState :: Init ,
291387 }
292388 }
293389}
294390
391+ #[ cfg( test) ]
392+ impl From < ABI > for Compatibility {
393+ fn from ( abi : ABI ) -> Self {
394+ Self :: from ( LandlockStatus :: from ( abi) )
395+ }
396+ }
397+
295398impl Compatibility {
296399 // Compatibility is a semi-opaque struct.
297400 #[ allow( clippy:: new_without_default) ]
298401 pub ( crate ) fn new ( ) -> Self {
299- ABI :: new_current ( ) . into ( )
402+ LandlockStatus :: current ( ) . into ( )
300403 }
301404
302405 pub ( crate ) fn update ( & mut self , state : CompatState ) {
303406 self . state . update ( state) ;
304407 }
305408
306409 pub ( crate ) fn abi ( & self ) -> ABI {
307- self . abi
410+ self . status . into ( )
411+ }
412+
413+ pub ( crate ) fn status ( & self ) -> LandlockStatus {
414+ self . status
308415 }
309416}
310417
0 commit comments