Skip to content

Commit f734afd

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. For instance, the variable `abi = 4` represents the highest version of the Landlock ABI in a configuration file, 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"] Add new ValueAccess enums and AbiGroup trait to factor out common code. Replace get_fs_read_execute() and get_fs_read_write() with AbiGroupFs::ReadExecute and AbiGroupFs::ReadWrite. Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent aef7aa8 commit f734afd

File tree

8 files changed

+862
-64
lines changed

8 files changed

+862
-64
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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@
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": [
19+
"abi.all",
20+
"abi.read_execute",
21+
"abi.read_write",
1422
"execute",
1523
"write_file",
1624
"read_file",
@@ -50,6 +58,7 @@
5058
"accessNet": {
5159
"type": "string",
5260
"enum": [
61+
"abi.all",
5362
"bind_tcp",
5463
"connect_tcp",
5564
"v4.all",
@@ -60,13 +69,17 @@
6069
"scope": {
6170
"type": "string",
6271
"enum": [
72+
"abi.all",
6373
"abstract_unix_socket",
6474
"signal",
6575
"v6.all"
6676
]
6777
}
6878
},
6979
"properties": {
80+
"abi": {
81+
"$ref": "#/definitions/abi"
82+
},
7083
"variable": {
7184
"type": "array",
7285
"minItems": 1,

src/config.rs

Lines changed: 84 additions & 9 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,23 +78,23 @@ 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+
.map(|access| access.resolve_bitflags(config.abi))
82+
.transpose()?
7883
.unwrap_or_default();
7984
config.handled_net |= ruleset
8085
.handledAccessNet
81-
.as_ref()
82-
.map(BitFlags::<AccessNet>::from)
86+
.map(|access| access.resolve_bitflags(config.abi))
87+
.transpose()?
8388
.unwrap_or_default();
8489
config.scoped |= ruleset
8590
.scoped
86-
.as_ref()
87-
.map(BitFlags::<Scope>::from)
91+
.map(|scoped| scoped.resolve_bitflags(config.abi))
92+
.transpose()?
8893
.unwrap_or_default();
8994
}
9095

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

9499
/* Automatically augment and keep the ruleset consistent. */
95100
config.handled_fs |= access;
@@ -104,7 +109,7 @@ impl TryFrom<NonEmptyStruct<JsonConfig>> for Config {
104109
}
105110

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

109114
/* Automatically augment and keep the ruleset consistent. */
110115
config.handled_net |= access;
@@ -198,6 +203,7 @@ impl Config {
198203
// could not be updated with public methods (e.g. compose).
199204
fn empty() -> Self {
200205
Self {
206+
abi: Default::default(),
201207
variables: Default::default(),
202208
handled_fs: Default::default(),
203209
handled_net: Default::default(),
@@ -274,6 +280,14 @@ impl Config {
274280
self.handled_fs &= other.handled_fs;
275281
self.handled_net &= other.handled_net;
276282
self.scoped &= other.scoped;
283+
284+
// Fifth step: downgrade the ABI version.
285+
self.abi = match (self.abi, other.abi) {
286+
(Some(a), Some(b)) => Some(a.min(b)),
287+
(Some(a), None) => Some(a),
288+
(None, Some(b)) => Some(b),
289+
(None, None) => None,
290+
};
277291
}
278292

279293
pub fn parse_json<R>(reader: R) -> Result<Self, ParseJsonError>
@@ -442,6 +456,7 @@ impl ResolvedConfig {
442456
impl TryFrom<Config> for ResolvedConfig {
443457
type Error = ResolveError;
444458

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

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)