[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