Skip to content

Commit f28b67f

Browse files
committed
Use abi.* synthetic variables
TODO: Remove patch.cartes-io once this PR land: landlock-lsm/rust-landlock#108 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 f28b67f

File tree

8 files changed

+648
-50
lines changed

8 files changed

+648
-50
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 & 5 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();
@@ -74,12 +79,16 @@ impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
7479
config.handled_fs |= ruleset
7580
.handledAccessFs
7681
.as_ref()
77-
.map(BitFlags::<AccessFs>::from)
82+
// TODO: Test .map(|access| access.into())
83+
.map(|access| access.into_access_fs(config.abi))
84+
.transpose()?
7885
.unwrap_or_default();
7986
config.handled_net |= ruleset
8087
.handledAccessNet
8188
.as_ref()
82-
.map(BitFlags::<AccessNet>::from)
89+
// TODO: Test .map(|access| access.into())
90+
.map(|access| access.into_access_net(config.abi))
91+
.transpose()?
8392
.unwrap_or_default();
8493
config.scoped |= ruleset
8594
.scoped
@@ -89,7 +98,7 @@ impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
8998
}
9099

91100
for path_beneath in json.pathBeneath.unwrap_or_default() {
92-
let access: BitFlags<AccessFs> = (&path_beneath.allowedAccess).into();
101+
let access = path_beneath.allowedAccess.into_access_fs(config.abi)?;
93102

94103
/* Automatically augment and keep the ruleset consistent. */
95104
config.handled_fs |= access;
@@ -104,7 +113,7 @@ impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
104113
}
105114

106115
for net_port in json.netPort.unwrap_or_default() {
107-
let access: BitFlags<AccessNet> = (&net_port.allowedAccess).into();
116+
let access = net_port.allowedAccess.into_access_net(config.abi)?;
108117

109118
/* Automatically augment and keep the ruleset consistent. */
110119
config.handled_net |= access;
@@ -198,6 +207,7 @@ impl Config {
198207
// could not be updated with public methods (e.g. compose).
199208
fn empty() -> Self {
200209
Self {
210+
abi: Default::default(),
201211
variables: Default::default(),
202212
handled_fs: Default::default(),
203213
handled_net: Default::default(),
@@ -274,6 +284,14 @@ impl Config {
274284
self.handled_fs &= other.handled_fs;
275285
self.handled_net &= other.handled_net;
276286
self.scoped &= other.scoped;
287+
288+
// Fifth step: downgrade the ABI version.
289+
self.abi = match (self.abi, other.abi) {
290+
(Some(a), Some(b)) => Some(a.min(b)),
291+
(Some(a), None) => Some(a),
292+
(None, Some(b)) => Some(b),
293+
(None, None) => None,
294+
};
277295
}
278296

279297
pub fn parse_json<R>(reader: R) -> Result<Self, ParseJsonError>
@@ -442,6 +460,7 @@ impl ResolvedConfig {
442460
impl TryFrom<Config> for ResolvedConfig {
443461
type Error = ResolveError;
444462

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

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)