Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

landlock: Add abstract UNIX socket scoping

Introduce a new "scoped" member to landlock_ruleset_attr that can
specify LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET to restrict connection to
abstract UNIX sockets from a process outside of the socket's domain.

Two hooks are implemented to enforce these restrictions:
unix_stream_connect and unix_may_send.

Closes: https://github.com/landlock-lsm/linux/issues/7
Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
Link: https://lore.kernel.org/r/5f7ad85243b78427242275b93481cfc7c127764b.1725494372.git.fahimitahera@gmail.com
[mic: Fix commit message formatting, improve documentation, simplify
hook_unix_may_send(), and cosmetic fixes including rename of
LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET]
Co-developed-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Mickaël Salaün <mic@digikod.net>

authored by

Tahera Fahimi and committed by
Mickaël Salaün
21d52e29 a430d95c

+208 -9
+27
include/uapi/linux/landlock.h
··· 44 44 * flags`_). 45 45 */ 46 46 __u64 handled_access_net; 47 + /** 48 + * @scoped: Bitmask of scopes (cf. `Scope flags`_) 49 + * restricting a Landlock domain from accessing outside 50 + * resources (e.g. IPCs). 51 + */ 52 + __u64 scoped; 47 53 }; 48 54 49 55 /* ··· 280 274 #define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0) 281 275 #define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1) 282 276 /* clang-format on */ 277 + 278 + /** 279 + * DOC: scope 280 + * 281 + * Scope flags 282 + * ~~~~~~~~~~~ 283 + * 284 + * These flags enable to isolate a sandboxed process from a set of IPC actions. 285 + * Setting a flag for a ruleset will isolate the Landlock domain to forbid 286 + * connections to resources outside the domain. 287 + * 288 + * Scopes: 289 + * 290 + * - %LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: Restrict a sandboxed process from 291 + * connecting to an abstract UNIX socket created by a process outside the 292 + * related Landlock domain (e.g. a parent domain or a non-sandboxed process). 293 + */ 294 + /* clang-format off */ 295 + #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0) 296 + /* clang-format on*/ 297 + 283 298 #endif /* _UAPI_LINUX_LANDLOCK_H */
+3
security/landlock/limits.h
··· 26 26 #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) 27 27 #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) 28 28 29 + #define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET 30 + #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) 31 + #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) 29 32 /* clang-format on */ 30 33 31 34 #endif /* _SECURITY_LANDLOCK_LIMITS_H */
+5 -2
security/landlock/ruleset.c
··· 52 52 53 53 struct landlock_ruleset * 54 54 landlock_create_ruleset(const access_mask_t fs_access_mask, 55 - const access_mask_t net_access_mask) 55 + const access_mask_t net_access_mask, 56 + const access_mask_t scope_mask) 56 57 { 57 58 struct landlock_ruleset *new_ruleset; 58 59 59 60 /* Informs about useless ruleset. */ 60 - if (!fs_access_mask && !net_access_mask) 61 + if (!fs_access_mask && !net_access_mask && !scope_mask) 61 62 return ERR_PTR(-ENOMSG); 62 63 new_ruleset = create_ruleset(1); 63 64 if (IS_ERR(new_ruleset)) ··· 67 66 landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0); 68 67 if (net_access_mask) 69 68 landlock_add_net_access_mask(new_ruleset, net_access_mask, 0); 69 + if (scope_mask) 70 + landlock_add_scope_mask(new_ruleset, scope_mask, 0); 70 71 return new_ruleset; 71 72 } 72 73
+23 -1
security/landlock/ruleset.h
··· 35 35 static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); 36 36 /* Makes sure all network access rights can be stored. */ 37 37 static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET); 38 + /* Makes sure all scoped rights can be stored. */ 39 + static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE); 38 40 /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */ 39 41 static_assert(sizeof(unsigned long) >= sizeof(access_mask_t)); 40 42 ··· 44 42 struct access_masks { 45 43 access_mask_t fs : LANDLOCK_NUM_ACCESS_FS; 46 44 access_mask_t net : LANDLOCK_NUM_ACCESS_NET; 45 + access_mask_t scope : LANDLOCK_NUM_SCOPE; 47 46 }; 48 47 49 48 typedef u16 layer_mask_t; ··· 236 233 237 234 struct landlock_ruleset * 238 235 landlock_create_ruleset(const access_mask_t access_mask_fs, 239 - const access_mask_t access_mask_net); 236 + const access_mask_t access_mask_net, 237 + const access_mask_t scope_mask); 240 238 241 239 void landlock_put_ruleset(struct landlock_ruleset *const ruleset); 242 240 void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); ··· 284 280 ruleset->access_masks[layer_level].net |= net_mask; 285 281 } 286 282 283 + static inline void 284 + landlock_add_scope_mask(struct landlock_ruleset *const ruleset, 285 + const access_mask_t scope_mask, const u16 layer_level) 286 + { 287 + access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE; 288 + 289 + /* Should already be checked in sys_landlock_create_ruleset(). */ 290 + WARN_ON_ONCE(scope_mask != mask); 291 + ruleset->access_masks[layer_level].scope |= mask; 292 + } 293 + 287 294 static inline access_mask_t 288 295 landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset, 289 296 const u16 layer_level) ··· 316 301 const u16 layer_level) 317 302 { 318 303 return ruleset->access_masks[layer_level].net; 304 + } 305 + 306 + static inline access_mask_t 307 + landlock_get_scope_mask(const struct landlock_ruleset *const ruleset, 308 + const u16 layer_level) 309 + { 310 + return ruleset->access_masks[layer_level].scope; 319 311 } 320 312 321 313 bool landlock_unmask_layers(const struct landlock_rule *const rule,
+12 -5
security/landlock/syscalls.c
··· 97 97 */ 98 98 ruleset_size = sizeof(ruleset_attr.handled_access_fs); 99 99 ruleset_size += sizeof(ruleset_attr.handled_access_net); 100 + ruleset_size += sizeof(ruleset_attr.scoped); 100 101 BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); 101 - BUILD_BUG_ON(sizeof(ruleset_attr) != 16); 102 + BUILD_BUG_ON(sizeof(ruleset_attr) != 24); 102 103 103 104 path_beneath_size = sizeof(path_beneath_attr.allowed_access); 104 105 path_beneath_size += sizeof(path_beneath_attr.parent_fd); ··· 150 149 .write = fop_dummy_write, 151 150 }; 152 151 153 - #define LANDLOCK_ABI_VERSION 5 152 + #define LANDLOCK_ABI_VERSION 6 154 153 155 154 /** 156 155 * sys_landlock_create_ruleset - Create a new ruleset ··· 171 170 * Possible returned errors are: 172 171 * 173 172 * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; 174 - * - %EINVAL: unknown @flags, or unknown access, or too small @size; 175 - * - %E2BIG or %EFAULT: @attr or @size inconsistencies; 173 + * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size; 174 + * - %E2BIG: @attr or @size inconsistencies; 175 + * - %EFAULT: @attr or @size inconsistencies; 176 176 * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. 177 177 */ 178 178 SYSCALL_DEFINE3(landlock_create_ruleset, ··· 215 213 LANDLOCK_MASK_ACCESS_NET) 216 214 return -EINVAL; 217 215 216 + /* Checks IPC scoping content (and 32-bits cast). */ 217 + if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE) 218 + return -EINVAL; 219 + 218 220 /* Checks arguments and transforms to kernel struct. */ 219 221 ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, 220 - ruleset_attr.handled_access_net); 222 + ruleset_attr.handled_access_net, 223 + ruleset_attr.scoped); 221 224 if (IS_ERR(ruleset)) 222 225 return PTR_ERR(ruleset); 223 226
+137
security/landlock/task.c
··· 13 13 #include <linux/lsm_hooks.h> 14 14 #include <linux/rcupdate.h> 15 15 #include <linux/sched.h> 16 + #include <net/af_unix.h> 17 + #include <net/sock.h> 16 18 17 19 #include "common.h" 18 20 #include "cred.h" ··· 110 108 return task_ptrace(parent, current); 111 109 } 112 110 111 + /** 112 + * domain_is_scoped - Checks if the client domain is scoped in the same 113 + * domain as the server. 114 + * 115 + * @client: IPC sender domain. 116 + * @server: IPC receiver domain. 117 + * @scope: The scope restriction criteria. 118 + * 119 + * Returns: True if the @client domain is scoped to access the @server, 120 + * unless the @server is also scoped in the same domain as @client. 121 + */ 122 + static bool domain_is_scoped(const struct landlock_ruleset *const client, 123 + const struct landlock_ruleset *const server, 124 + access_mask_t scope) 125 + { 126 + int client_layer, server_layer; 127 + struct landlock_hierarchy *client_walker, *server_walker; 128 + 129 + /* Quick return if client has no domain */ 130 + if (WARN_ON_ONCE(!client)) 131 + return false; 132 + 133 + client_layer = client->num_layers - 1; 134 + client_walker = client->hierarchy; 135 + /* 136 + * client_layer must be a signed integer with greater capacity 137 + * than client->num_layers to ensure the following loop stops. 138 + */ 139 + BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers)); 140 + 141 + server_layer = server ? (server->num_layers - 1) : -1; 142 + server_walker = server ? server->hierarchy : NULL; 143 + 144 + /* 145 + * Walks client's parent domains down to the same hierarchy level 146 + * as the server's domain, and checks that none of these client's 147 + * parent domains are scoped. 148 + */ 149 + for (; client_layer > server_layer; client_layer--) { 150 + if (landlock_get_scope_mask(client, client_layer) & scope) 151 + return true; 152 + 153 + client_walker = client_walker->parent; 154 + } 155 + /* 156 + * Walks server's parent domains down to the same hierarchy level as 157 + * the client's domain. 158 + */ 159 + for (; server_layer > client_layer; server_layer--) 160 + server_walker = server_walker->parent; 161 + 162 + for (; client_layer >= 0; client_layer--) { 163 + if (landlock_get_scope_mask(client, client_layer) & scope) { 164 + /* 165 + * Client and server are at the same level in the 166 + * hierarchy. If the client is scoped, the request is 167 + * only allowed if this domain is also a server's 168 + * ancestor. 169 + */ 170 + return server_walker != client_walker; 171 + } 172 + client_walker = client_walker->parent; 173 + server_walker = server_walker->parent; 174 + } 175 + return false; 176 + } 177 + 178 + static bool sock_is_scoped(struct sock *const other, 179 + const struct landlock_ruleset *const domain) 180 + { 181 + const struct landlock_ruleset *dom_other; 182 + 183 + /* The credentials will not change. */ 184 + lockdep_assert_held(&unix_sk(other)->lock); 185 + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; 186 + return domain_is_scoped(domain, dom_other, 187 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 188 + } 189 + 190 + static bool is_abstract_socket(struct sock *const sock) 191 + { 192 + struct unix_address *addr = unix_sk(sock)->addr; 193 + 194 + if (!addr) 195 + return false; 196 + 197 + if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 && 198 + addr->name->sun_path[0] == '\0') 199 + return true; 200 + 201 + return false; 202 + } 203 + 204 + static int hook_unix_stream_connect(struct sock *const sock, 205 + struct sock *const other, 206 + struct sock *const newsk) 207 + { 208 + const struct landlock_ruleset *const dom = 209 + landlock_get_current_domain(); 210 + 211 + /* Quick return for non-landlocked tasks. */ 212 + if (!dom) 213 + return 0; 214 + 215 + if (is_abstract_socket(other) && sock_is_scoped(other, dom)) 216 + return -EPERM; 217 + 218 + return 0; 219 + } 220 + 221 + static int hook_unix_may_send(struct socket *const sock, 222 + struct socket *const other) 223 + { 224 + const struct landlock_ruleset *const dom = 225 + landlock_get_current_domain(); 226 + 227 + if (!dom) 228 + return 0; 229 + 230 + /* 231 + * Checks if this datagram socket was already allowed to be connected 232 + * to other. 233 + */ 234 + if (unix_peer(sock->sk) == other->sk) 235 + return 0; 236 + 237 + if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom)) 238 + return -EPERM; 239 + 240 + return 0; 241 + } 242 + 113 243 static struct security_hook_list landlock_hooks[] __ro_after_init = { 114 244 LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check), 115 245 LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme), 246 + 247 + LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect), 248 + LSM_HOOK_INIT(unix_may_send, hook_unix_may_send), 116 249 }; 117 250 118 251 __init void landlock_add_task_hooks(void)
+1 -1
tools/testing/selftests/landlock/base_test.c
··· 76 76 const struct landlock_ruleset_attr ruleset_attr = { 77 77 .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, 78 78 }; 79 - ASSERT_EQ(5, landlock_create_ruleset(NULL, 0, 79 + ASSERT_EQ(6, landlock_create_ruleset(NULL, 0, 80 80 LANDLOCK_CREATE_RULESET_VERSION)); 81 81 82 82 ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,