[PATCH v10 4/9] samples/landlock: Add quiet flag support to sandboxer
Tingmao Wang
m at maowtm.org
Fri Jun 12 01:12:58 UTC 2026
On 6/8/26 23:41, Mickaël Salaün wrote:
> As for LL_FORCE_LOG, using a QUIET flag not supported should exit with
> an error.
As in, if the current kernel doesn't support quiet flags? Added check.
>
> On Mon, Jun 01, 2026 at 01:00:38AM +0100, Tingmao Wang wrote:
>> Adds ability to set which access bits to quiet via LL_*_QUIET_ACCESS (FS,
>> NET or SCOPED), and attach quiet flags to individual objects via
>> LL_*_QUIET for FS and NET.
>>
>> Signed-off-by: Tingmao Wang <m at maowtm.org>
>> ---
>>
>> Changes in v10:
>> - Remove stray __attribute__((fallthrough)); (Thanks Justin for
>> spotting)
>>
>> Changes in v9:
>> - Add udp connect / bind quiet flag support
>>
>> Changes in v8:
>> - Rebase on top of mic/next
>> - populate_ruleset_net() already does not require the env var to be
>> present, so remove redundant comment and check above
>> populate_ruleset_net(ENV_NET_QUIET_NAME, ...).
>>
>> Changes in v6:
>> - Make populate_ruleset_{fs,net} take a flags argument instead of a bool
>> quiet (suggested by Justin Suess)
>> - Fix if braces style
>>
>> Changes in v3:
>> - Minor change to the above commit message.
>>
>> Changes in v2:
>> - Added new environment variables to control which quiet access bits to
>> set on the rule, and populate quiet_access_* from it.
>> - Added support for quieting net rules and scoped access. Renamed patch
>> title.
>> - Increment ABI version
>>
>> samples/landlock/sandboxer.c | 133 ++++++++++++++++++++++++++++++++---
>> 1 file changed, 122 insertions(+), 11 deletions(-)
>>
>> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
>> index 94e399e6b146..73a81ecd3696 100644
>> --- a/samples/landlock/sandboxer.c
>> +++ b/samples/landlock/sandboxer.c
>> @@ -58,9 +58,14 @@ static inline int landlock_restrict_self(const int ruleset_fd,
>>
>> #define ENV_FS_RO_NAME "LL_FS_RO"
>> #define ENV_FS_RW_NAME "LL_FS_RW"
>> +#define ENV_FS_QUIET_NAME "LL_FS_QUIET"
>> +#define ENV_FS_QUIET_ACCESS_NAME "LL_FS_QUIET_ACCESS"
>> #define ENV_TCP_BIND_NAME "LL_TCP_BIND"
>> #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
>> +#define ENV_NET_QUIET_NAME "LL_NET_QUIET"
>> +#define ENV_NET_QUIET_ACCESS_NAME "LL_NET_QUIET_ACCESS"
>> #define ENV_SCOPED_NAME "LL_SCOPED"
>> +#define ENV_SCOPED_QUIET_ACCESS_NAME "LL_SCOPED_QUIET_ACCESS"
>> #define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
>> #define ENV_UDP_BIND_NAME "LL_UDP_BIND"
>> #define ENV_UDP_CONNECT_SEND_NAME "LL_UDP_CONNECT_SEND"
>> @@ -119,7 +124,7 @@ static int parse_path(char *env_path, const char ***const path_list)
>> /* clang-format on */
>>
>> static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
>> - const __u64 allowed_access)
>> + const __u64 allowed_access, __u32 flags)
>> {
>> int num_paths, i, ret = 1;
>> char *env_path_name;
>> @@ -169,7 +174,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
>> if (!S_ISDIR(statbuf.st_mode))
>> path_beneath.allowed_access &= ACCESS_FILE;
>> if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
>> - &path_beneath, 0)) {
>> + &path_beneath, flags)) {
>> fprintf(stderr,
>> "Failed to update the ruleset with \"%s\": %s\n",
>> path_list[i], strerror(errno));
>> @@ -187,7 +192,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
>> }
>>
>> static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>> - const __u64 allowed_access)
>> + const __u64 allowed_access, __u32 flags)
>> {
>> int ret = 1;
>> char *env_port_name, *env_port_name_next, *strport;
>> @@ -215,7 +220,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>> }
>> net_port.port = port;
>> if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &net_port, 0)) {
>> + &net_port, flags)) {
>> fprintf(stderr,
>> "Failed to update the ruleset with port \"%llu\": %s\n",
>> net_port.port, strerror(errno));
>> @@ -303,6 +308,58 @@ static bool check_ruleset_scope(const char *const env_var,
>>
>> /* clang-format on */
>>
>> +static int add_quiet_access(__u64 *const quiet_access,
>> + const __u64 handled_access,
>> + const char *const env_var, const bool default_all)
>> +{
>> + char *env_quiet_access, *env_quiet_access_next, *str_access;
>> +
>> + if (default_all)
>> + *quiet_access = handled_access;
>> + else
>> + *quiet_access = 0;
>> +
>> + env_quiet_access = getenv(env_var);
>> + if (!env_quiet_access)
>> + return 0;
>> +
>> + env_quiet_access = strdup(env_quiet_access);
>> + env_quiet_access_next = env_quiet_access;
>> + unsetenv(env_var);
>> + *quiet_access = 0;
>> +
>> + while ((str_access = strsep(&env_quiet_access_next, ENV_DELIMITER))) {
>> + if (strcmp(str_access, "") == 0)
>> + continue;
>> + else if (strcmp(str_access, "r") == 0)
>> + *quiet_access |= ACCESS_FS_ROUGHLY_READ;
>> + else if (strcmp(str_access, "w") == 0)
>> + *quiet_access |= ACCESS_FS_ROUGHLY_WRITE;
>> + else if (strcmp(str_access, "b") == 0)
>> + *quiet_access |= LANDLOCK_ACCESS_NET_BIND_TCP;
>
> What happen if we set "b" in LL_FS_QUIET_ACCESS?
>
>> + else if (strcmp(str_access, "c") == 0)
>> + *quiet_access |= LANDLOCK_ACCESS_NET_CONNECT_TCP;
>> + else if (strcmp(str_access, "ub") == 0)
>
> I don't really like these access-right names, they are not consistent.
> All these env variables add a lot of complexity too. What about just
> being able to quiet a path or a port? That would mean renaming
> LL_FS_QUIET_ACCESS to LL_FS_QUIET.
I'm happy to remove LL_{FS,NET}_QUIET_ACCESS and just have
LL_{FS,NET}_QUIET quiet all access, but then we lose the ability to demo
"quiet only read but still log write" via the sandboxer.
I do agree the "b in LL_FS_QUIET_ACCESS" case is weird, so maybe we can
just have one LL_QUIET_ACCESS variable?
Also, the names are like this because I tried to mimic the one-letter
scoped access, but we could use e.g.
LL_QUIET_ACCESS=read:write:tcp_bind:tcp_connect:udp_bind:udp_connect:abstract_unix_socket:signal
Do you want to keep the ability to specify LL_QUIET_ACCESS? (I think it's
useful for demo, since I expect "quiet read but log write denials" to be
quite common.)
>
> Anyway, all should be unsetenv() unconditionally.
I think they are already all unsetenv()'d already (checked with
/usr/bin/env), do you mean to make them not conditional on the env
existing in the first place? I followed how populate_ruleset_{fs,net}
works and those two functions currently do conditional unsetenv(),
althouygh check_ruleset_scope() does it unconditionally.
>
>> + *quiet_access |= LANDLOCK_ACCESS_NET_BIND_UDP;
>> + else if (strcmp(str_access, "uc") == 0)
>> + *quiet_access |= LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
>> + else if (strcmp(str_access, "a") == 0)
>> + *quiet_access |= LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
>> + else if (strcmp(str_access, "s") == 0)
>> + *quiet_access |= LANDLOCK_SCOPE_SIGNAL;
>> + else {
>> + fprintf(stderr, "Unknown quiet access \"%s\"\n",
>> + str_access);
>> + free(env_quiet_access);
>> + return -1;
>> + }
>> + }
>> +
>> + free(env_quiet_access);
>> + *quiet_access &= handled_access;
>> + return 0;
>> +}
>> +
>> #define LANDLOCK_ABI_LAST 10
>>
>> #define XSTR(s) #s
>> [...]
More information about the Linux-security-module-archive
mailing list