diff options
Diffstat (limited to 'security/landlock/task.c')
-rw-r--r-- | security/landlock/task.c | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/security/landlock/task.c b/security/landlock/task.c index 849f5123610b..4f8013ca412e 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -13,6 +13,8 @@ #include <linux/lsm_hooks.h> #include <linux/rcupdate.h> #include <linux/sched.h> +#include <net/af_unix.h> +#include <net/sock.h> #include "common.h" #include "cred.h" @@ -108,9 +110,144 @@ static int hook_ptrace_traceme(struct task_struct *const parent) return task_ptrace(parent, current); } +/** + * domain_is_scoped - Checks if the client domain is scoped in the same + * domain as the server. + * + * @client: IPC sender domain. + * @server: IPC receiver domain. + * @scope: The scope restriction criteria. + * + * Returns: True if the @client domain is scoped to access the @server, + * unless the @server is also scoped in the same domain as @client. + */ +static bool domain_is_scoped(const struct landlock_ruleset *const client, + const struct landlock_ruleset *const server, + access_mask_t scope) +{ + int client_layer, server_layer; + struct landlock_hierarchy *client_walker, *server_walker; + + /* Quick return if client has no domain */ + if (WARN_ON_ONCE(!client)) + return false; + + client_layer = client->num_layers - 1; + client_walker = client->hierarchy; + /* + * client_layer must be a signed integer with greater capacity + * than client->num_layers to ensure the following loop stops. + */ + BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers)); + + server_layer = server ? (server->num_layers - 1) : -1; + server_walker = server ? server->hierarchy : NULL; + + /* + * Walks client's parent domains down to the same hierarchy level + * as the server's domain, and checks that none of these client's + * parent domains are scoped. + */ + for (; client_layer > server_layer; client_layer--) { + if (landlock_get_scope_mask(client, client_layer) & scope) + return true; + + client_walker = client_walker->parent; + } + /* + * Walks server's parent domains down to the same hierarchy level as + * the client's domain. + */ + for (; server_layer > client_layer; server_layer--) + server_walker = server_walker->parent; + + for (; client_layer >= 0; client_layer--) { + if (landlock_get_scope_mask(client, client_layer) & scope) { + /* + * Client and server are at the same level in the + * hierarchy. If the client is scoped, the request is + * only allowed if this domain is also a server's + * ancestor. + */ + return server_walker != client_walker; + } + client_walker = client_walker->parent; + server_walker = server_walker->parent; + } + return false; +} + +static bool sock_is_scoped(struct sock *const other, + const struct landlock_ruleset *const domain) +{ + const struct landlock_ruleset *dom_other; + + /* The credentials will not change. */ + lockdep_assert_held(&unix_sk(other)->lock); + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; + return domain_is_scoped(domain, dom_other, + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); +} + +static bool is_abstract_socket(struct sock *const sock) +{ + struct unix_address *addr = unix_sk(sock)->addr; + + if (!addr) + return false; + + if (addr->len >= offsetof(struct sockaddr_un, sun_path) + 1 && + addr->name->sun_path[0] == '\0') + return true; + + return false; +} + +static int hook_unix_stream_connect(struct sock *const sock, + struct sock *const other, + struct sock *const newsk) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); + + /* Quick return for non-landlocked tasks. */ + if (!dom) + return 0; + + if (is_abstract_socket(other) && sock_is_scoped(other, dom)) + return -EPERM; + + return 0; +} + +static int hook_unix_may_send(struct socket *const sock, + struct socket *const other) +{ + const struct landlock_ruleset *const dom = + landlock_get_current_domain(); + + if (!dom) + return 0; + + /* + * Checks if this datagram socket was already allowed to be connected + * to other. + */ + if (unix_peer(sock->sk) == other->sk) + return 0; + + if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom)) + return -EPERM; + + return 0; +} + static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check), LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme), + + LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect), + LSM_HOOK_INIT(unix_may_send, hook_unix_may_send), }; __init void landlock_add_task_hooks(void) |