Skip to content

Commit 5cecd80

Browse files
committed
Use abi.* synthetic variables
TODO: Remove patch.cartes-io once this PR land: landlock-lsm/rust-landlock#108 TODO: Factor out code In preparation to replace the "vN." prefixes with a global max ABI version. This new approach is flexible enough and simpler. This is now possible thanks to the composition feature (each file can have a dedicated max ABI) and its similar to the use of a local variable. The variable `abi = 4` is the highest version of the Landlock ABI, which should replace the hardcoded v4 uses. This is convenient to update configurations to newest Landlock features by only updating one line instead of all use of vN. Example: abi = 4 [[ruleset]] handled_access_fs = ["abi.all"] [[path_beneath]] allowed_access = ["abi.read_execute"] parent = ["/usr"] Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent aef7aa8 commit 5cecd80

File tree

8 files changed

+648
-52
lines changed

8 files changed

+648
-52
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,6 @@ lazy_static = "1.5.0"
4444
[[example]]
4545
name = "sandboxer"
4646
required-features = ["toml"]
47+
48+
[patch.crates-io]
49+
landlock = { git = "https://github.com/landlock-lsm/rust-landlock", rev = "19f01a99752607e0f6b1872edf86690982c0e920" }

examples/micro-var.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
abi = 5
2+
13
[[variable]]
24
name = "tmp"
35
literal = ["/tmp", "/var/tmp"]
@@ -12,10 +14,10 @@ literal = ["/etc"]
1214

1315
# Main system file hierarchies can be read and executed, including the current path (for test only).
1416
[[path_beneath]]
15-
allowed_access = ["v5.read_execute"]
17+
allowed_access = ["abi.read_execute"]
1618
parent = [".", "/bin", "/lib", "/usr", "/dev", "/proc", "${config}"]
1719

1820
# Only allow writing to temporary and home directories.
1921
[[path_beneath]]
20-
allowed_access = ["v5.read_write"]
22+
allowed_access = ["abi.read_write"]
2123
parent = ["${tmp}", "${home}"]

schema/landlockconfig.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
"minimum": 0,
99
"maximum": 18446744073709551615
1010
},
11+
"abi": {
12+
"type": "integer",
13+
"minimum": 1,
14+
"maximum": 2147483647
15+
},
1116
"accessFs": {
1217
"type": "string",
1318
"enum": [
@@ -44,7 +49,10 @@
4449
"v5.read_write",
4550
"v6.all",
4651
"v6.read_execute",
47-
"v6.read_write"
52+
"v6.read_write",
53+
"abi.all",
54+
"abi.read_execute",
55+
"abi.read_write"
4856
]
4957
},
5058
"accessNet": {
@@ -54,7 +62,8 @@
5462
"connect_tcp",
5563
"v4.all",
5664
"v5.all",
57-
"v6.all"
65+
"v6.all",
66+
"abi.all"
5867
]
5968
},
6069
"scope": {
@@ -67,6 +76,9 @@
6776
}
6877
},
6978
"properties": {
79+
"abi": {
80+
"$ref": "#/definitions/abi"
81+
},
7082
"variable": {
7183
"type": "array",
7284
"minItems": 1,

src/config.rs

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::parser::{JsonConfig, TemplateString, TomlConfig};
55
use crate::variable::{NameError, ResolveError, Variables, VecStringIterator};
66
use landlock::{
77
AccessFs, AccessNet, BitFlags, NetPort, PathBeneath, PathFd, PathFdError, Ruleset, RulesetAttr,
8-
RulesetCreated, RulesetCreatedAttr, RulesetError, Scope,
8+
RulesetCreated, RulesetCreatedAttr, RulesetError, Scope, ABI,
99
};
1010
use std::collections::BTreeMap;
1111
use std::fs::{self, File};
@@ -27,6 +27,7 @@ pub enum BuildRulesetError {
2727
#[cfg_attr(test, derive(Default))]
2828
#[derive(Clone, Debug, PartialEq, Eq)]
2929
pub struct Config {
30+
pub(crate) abi: Option<ABI>,
3031
pub(crate) variables: Variables,
3132
pub(crate) handled_fs: BitFlags<AccessFs>,
3233
pub(crate) handled_net: BitFlags<AccessNet>,
@@ -53,6 +54,8 @@ pub struct ResolvedConfig {
5354
pub enum ConfigError {
5455
#[error(transparent)]
5556
Name(#[from] NameError),
57+
#[error(transparent)]
58+
Resolve(#[from] ResolveError),
5659
}
5760

5861
impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
@@ -62,6 +65,8 @@ impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
6265
let mut config = Self::empty();
6366
let json = json.into_inner();
6467

68+
config.abi = json.abi.map(Into::into);
69+
6570
for variable in json.variable.unwrap_or_default() {
6671
let name = variable.name.parse()?;
6772
let literal = variable.literal.unwrap_or_default();
@@ -73,13 +78,15 @@ impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
7378
let ruleset = ruleset.into_inner();
7479
config.handled_fs |= ruleset
7580
.handledAccessFs
76-
.as_ref()
77-
.map(BitFlags::<AccessFs>::from)
81+
// TODO: Test .map(|access| access.into())
82+
.map(|access| access.resolve_bitflags(config.abi))
83+
.transpose()?
7884
.unwrap_or_default();
7985
config.handled_net |= ruleset
8086
.handledAccessNet
81-
.as_ref()
82-
.map(BitFlags::<AccessNet>::from)
87+
// TODO: Test .map(|access| access.into())
88+
.map(|access| access.resolve_bitflags(config.abi))
89+
.transpose()?
8390
.unwrap_or_default();
8491
config.scoped |= ruleset
8592
.scoped
@@ -89,7 +96,7 @@ impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
8996
}
9097

9198
for path_beneath in json.pathBeneath.unwrap_or_default() {
92-
let access: BitFlags<AccessFs> = (&path_beneath.allowedAccess).into();
99+
let access = path_beneath.allowedAccess.resolve_bitflags(config.abi)?;
93100

94101
/* Automatically augment and keep the ruleset consistent. */
95102
config.handled_fs |= access;
@@ -104,7 +111,7 @@ impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
104111
}
105112

106113
for net_port in json.netPort.unwrap_or_default() {
107-
let access: BitFlags<AccessNet> = (&net_port.allowedAccess).into();
114+
let access = net_port.allowedAccess.resolve_bitflags(config.abi)?;
108115

109116
/* Automatically augment and keep the ruleset consistent. */
110117
config.handled_net |= access;
@@ -198,6 +205,7 @@ impl Config {
198205
// could not be updated with public methods (e.g. compose).
199206
fn empty() -> Self {
200207
Self {
208+
abi: Default::default(),
201209
variables: Default::default(),
202210
handled_fs: Default::default(),
203211
handled_net: Default::default(),
@@ -274,6 +282,14 @@ impl Config {
274282
self.handled_fs &= other.handled_fs;
275283
self.handled_net &= other.handled_net;
276284
self.scoped &= other.scoped;
285+
286+
// Fifth step: downgrade the ABI version.
287+
self.abi = match (self.abi, other.abi) {
288+
(Some(a), Some(b)) => Some(a.min(b)),
289+
(Some(a), None) => Some(a),
290+
(None, Some(b)) => Some(b),
291+
(None, None) => None,
292+
};
277293
}
278294

279295
pub fn parse_json<R>(reader: R) -> Result<Self, ParseJsonError>
@@ -442,6 +458,7 @@ impl ResolvedConfig {
442458
impl TryFrom<Config> for ResolvedConfig {
443459
type Error = ResolveError;
444460

461+
/// Resolve all (composed) variables but not local (synthetic) variables such as `abi.*`.
445462
fn try_from(config: Config) -> Result<Self, Self::Error> {
446463
let mut rules_path_beneath: BTreeMap<PathBuf, BitFlags<AccessFs>> = Default::default();
447464
for (path_beneath, access) in config.rules_path_beneath {
@@ -537,4 +554,64 @@ mod tests_compose {
537554
}
538555
);
539556
}
557+
558+
#[test]
559+
fn test_abi_none_none() {
560+
let c1 = Config {
561+
abi: None,
562+
..Default::default()
563+
};
564+
let mut c1_mut = c1.clone();
565+
let mut c2_mut = Config {
566+
abi: None,
567+
..Default::default()
568+
};
569+
570+
c1_mut.compose(&c2_mut);
571+
assert_eq!(c1_mut.abi, None);
572+
573+
// Test commutativity
574+
c2_mut.compose(&c1);
575+
assert_eq!(c1_mut, c2_mut);
576+
}
577+
578+
#[test]
579+
fn test_abi_some_none() {
580+
let c1 = Config {
581+
abi: Some(ABI::V2),
582+
..Default::default()
583+
};
584+
let mut c1_mut = c1.clone();
585+
let mut c2_mut = Config {
586+
abi: None,
587+
..Default::default()
588+
};
589+
590+
c1_mut.compose(&c2_mut);
591+
assert_eq!(c1_mut.abi, Some(ABI::V2));
592+
593+
// Test commutativity
594+
c2_mut.compose(&c1);
595+
assert_eq!(c1_mut, c2_mut);
596+
}
597+
598+
#[test]
599+
fn test_abi_some_some() {
600+
let c1 = Config {
601+
abi: Some(ABI::V1),
602+
..Default::default()
603+
};
604+
let mut c1_mut = c1.clone();
605+
let mut c2_mut = Config {
606+
abi: Some(ABI::V2),
607+
..Default::default()
608+
};
609+
610+
c1_mut.compose(&c2_mut);
611+
assert_eq!(c1_mut.abi, Some(ABI::V1));
612+
613+
// Test commutativity
614+
c2_mut.compose(&c1);
615+
assert_eq!(c1_mut, c2_mut);
616+
}
540617
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ mod tests_variable;
2222

2323
#[cfg(test)]
2424
mod tests_compose;
25+
26+
#[cfg(test)]
27+
mod tests_abi;

0 commit comments

Comments
 (0)