From patchwork Thu Sep 5 00:13:55 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tahera Fahimi X-Patchwork-Id: 13791549 Received: from mail-pf1-f175.google.com (mail-pf1-f175.google.com [209.85.210.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 616147FD; Thu, 5 Sep 2024 00:14:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495264; cv=none; b=fn0fGEYKu+RlUr2DO2Ma+C3WREXO5rmO8cMY3t/Afn7cjmJIcY2KNgqYs0qTQCSdJaBTZbPSIBJ3QHVtltqhoho2SXkp3V0+ZX6Z8T7/cdy2ZCEUVMRMsHdFmdHSnauaeRvanfcAVv+F+yMHI3ln0YUfOUUTq9jCCgBEkUoxyuk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495264; c=relaxed/simple; bh=1eLq1PP8iaPdS3jbuv0CdYf9v++4SKYAS2Y+QjeaXGo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=f30K8uhQnkI0q1AEaeKsuDI4pmJiPbE7ZcfuNV5opjbVGE7ynjnJiibNJlOx7vduzP6dlQ1qhXXW15l9ezfUX+5hr/cb0U4YK5/VSnoquppbQB1ds755rso0CPW1ydwax2tBnEUd3rLT/joXQ9lQJrGfr/F5GIGQ+Lo4vVZAMyA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=AW+1w+HS; arc=none smtp.client-ip=209.85.210.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="AW+1w+HS" Received: by mail-pf1-f175.google.com with SMTP id d2e1a72fcca58-715cdc7a153so150336b3a.0; Wed, 04 Sep 2024 17:14:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725495262; x=1726100062; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=WIng07xjLqeL9l9f8Vn7l19rTadVpmTgV54ssLpTYto=; b=AW+1w+HSMRVcB3nHKolG89IMQ8HkOs6IclX8X5YSFHz+RtjlqpUNLpz5Srx/XZYLag SD9uxk2IUcXpPq/O8nVART5rKV7SqmrNvBRtC7dHQm1at6Pse5h1uXbW4LbwEybaMz7w Fjg4G7hOFrcXmn8hxMj8Z4kpjSPW1xNa3w2+XD+LKsb3APNTG5df+SzZQEbFAqz+Snbd V2Smqlky+YOlzreTY/bUA9X5nh3Jot0TJYA0LT9N0J4LsBK+K/8l0GJMczIMUL8s8REG CbzpL4eM1cDmHofU/eOBLI/uWTaZK4r6mWEeJw6nPy1fP3CuGuBkLIgThdkpnw/Y9pkW 6PfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725495262; x=1726100062; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=WIng07xjLqeL9l9f8Vn7l19rTadVpmTgV54ssLpTYto=; b=V6Hv5mLoy7X7wyLFlM5VLsVMtkwWNqCXV6gWLC57BTZdnepPjqjtewFmr7T04+ZRBt 7pcjlpEF9MoGBKMKVZGYg9w++ZHsgc5ql73EOeCTOPvRzKsJZ0tnQIvBOn9TDqOqWxeN WoXi3WrozJ3wfzEQN5MoIsiARYJ1DP0zqAIQ1KZx6qQi0dWYtECIACeBOs1dCvtG7CCg 4OJW5xfwSbbjO1DPC87z5uf7aRRyZA1RVy/BjYP0KoUwh2AvHotrZ7ktDi3hgyHX1jxu fYRqDtByGXUxSmcQoTFQLrgLhyk3RzykWR4xDuv0xPMQAh3VBRrl+CA3/ZfVpA6dcQNF UdQg== X-Forwarded-Encrypted: i=1; AJvYcCUD6OB7JjD6uhvyGjRGP0V6/IKttv9q3YvRoDjINWDeeTgmr0Q7aKEEixpDFb2Rpp9fMFSYBxmj@vger.kernel.org, AJvYcCVrksYVqk0BJm/xNetPrdUcv0/JGWwO6WjaNkbdN+dyWjtlLTOFy6zdCUMqCyL8ZtObi3KqYKimV/TuMHA=@vger.kernel.org, AJvYcCXXVVv6Ectebwn+E5Jug4g9VdvOqVQ6tbMJMlDppRYwqV3nL2dd1qgptby+0NZDAgWfTrxL6F4ByXlsNoe62wjAJRe3AF2W@vger.kernel.org X-Gm-Message-State: AOJu0Yy/EkgbjtN2EPlhoVsECQpKFeXZIAJ7Ty04SYTCKDMteVhnubqB qBYn6MviNE2EAt4jM3zGoY4M4XiwU2dLxu6oE9ZkWr7/Uc59n+ZG X-Google-Smtp-Source: AGHT+IGXQWtGvJGVlcO023lqlimqVbmFXODZ4CQpQ1qaf64tbbJrw6EcZJeDwbLjdWriIVVP6tiKQg== X-Received: by 2002:aa7:88d5:0:b0:70d:2a1b:422c with SMTP id d2e1a72fcca58-7177a943322mr6547244b3a.7.1725495261440; Wed, 04 Sep 2024 17:14:21 -0700 (PDT) Received: from tahera-OptiPlex-5000.tail3bf47f.ts.net ([136.159.49.123]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71778534921sm2159781b3a.76.2024.09.04.17.14.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Sep 2024 17:14:20 -0700 (PDT) From: Tahera Fahimi To: outreachy@lists.linux.dev Cc: mic@digikod.net, gnoack@google.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, bjorn3_gh@protonmail.com, jannh@google.com, netdev@vger.kernel.org, Tahera Fahimi Subject: [PATCH v11 1/8] Landlock: Add abstract UNIX socket restriction Date: Wed, 4 Sep 2024 18:13:55 -0600 Message-Id: <5f7ad85243b78427242275b93481cfc7c127764b.1725494372.git.fahimitahera@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This patch introduces a new "scoped" attribute to the landlock_ruleset_attr that can specify "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" to scope abstract UNIX sockets from connecting to a process outside of the same Landlock domain. It implements two hooks, unix_stream_connect and unix_may_send to enforce this restriction. Closes: https://github.com/landlock-lsm/linux/issues/7 Signed-off-by: Tahera Fahimi --- v11: - For a connected abstract datagram socket, the hook_unix_may_send allows the socket to send a data. (it is treated as a connected stream socket) - Minor comment revision. v10: - Minor code improvement based on reviews on v9. v9: - Editting inline comments. - Major refactoring in domain_is_scoped() and is_abstract_socket v8: - Code refactoring (improve code readability, renaming variable, etc.) based on reviews by Mickaël Salaün on version 7. - Adding warn_on_once to check (impossible) inconsistencies. - Adding inline comments. - Adding check_unix_address_format to check if the scoping socket is an abstract UNIX sockets. v7: - Using socket's file credentials for both connected(STREAM) and non-connected(DGRAM) sockets. - Adding "domain_sock_scope" instead of the domain scoping mechanism used in ptrace ensures that if a server's domain is accessible from the client's domain (where the client is more privileged than the server), the client can connect to the server in all edge cases. - Removing debug codes. v6: - Removing curr_ruleset from landlock_hierarchy, and switching back to use the same domain scoping as ptrace. - code clean up. v5: - Renaming "LANDLOCK_*_ACCESS_SCOPE" to "LANDLOCK_*_SCOPE" - Adding curr_ruleset to hierarachy_ruleset structure to have access from landlock_hierarchy to its respective landlock_ruleset. - Using curr_ruleset to check if a domain is scoped while walking in the hierarchy of domains. - Modifying inline comments. v4: - Rebased on Günther's Patch: https://lore.kernel.org/all/20240610082115.1693267-1-gnoack@google.com/ so there is no need for "LANDLOCK_SHIFT_ACCESS_SCOPE", then it is removed. - Adding get_scope_accesses function to check all scoped access masks in a ruleset. - Using socket's file credentials instead of credentials stored in peer_cred for datagram sockets. (see discussion in [1]) - Modifying inline comments. V3: - Improving commit description. - Introducing "scoped" attribute to landlock_ruleset_attr for IPC scoping purpose, and adding related functions. - Changing structure of ruleset based on "scoped". - Removing rcu lock and using unix_sk lock instead. - Introducing scoping for datagram sockets in unix_may_send. V2: - Removing wrapper functions [1]https://lore.kernel.org/all/20240610.Aifee5ingugh@digikod.net/ --- include/uapi/linux/landlock.h | 28 ++++ security/landlock/limits.h | 3 + security/landlock/ruleset.c | 7 +- security/landlock/ruleset.h | 24 +++- security/landlock/syscalls.c | 17 ++- security/landlock/task.c | 136 +++++++++++++++++++ tools/testing/selftests/landlock/base_test.c | 2 +- 7 files changed, 208 insertions(+), 9 deletions(-) diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 2c8dbc74b955..dfd48d722834 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -44,6 +44,12 @@ struct landlock_ruleset_attr { * flags`_). */ __u64 handled_access_net; + /** + * @scoped: Bitmask of scopes (cf. `Scope flags`_) + * restricting a Landlock domain from accessing outside + * resources(e.g. IPCs). + */ + __u64 scoped; }; /* @@ -274,4 +280,26 @@ struct landlock_net_port_attr { #define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0) #define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1) /* clang-format on */ + +/** + * DOC: scope + * + * Scope flags + * ~~~~~~~~~~~ + * + * These flags enable to restrict a sandboxed process from a set of IPC + * actions. Setting a flag for a ruleset will isolate the Landlock domain + * to forbid connections to resources outside the domain. + * + * IPCs with scoped actions: + * + * - %LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET: Restrict a sandboxed process + * from connecting to an abstract unix socket created by a process + * outside the related Landlock domain (e.g. a parent domain or a + * non-sandboxed process). + */ +/* clang-format off */ +#define LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET (1ULL << 0) +/* clang-format on*/ + #endif /* _UAPI_LINUX_LANDLOCK_H */ diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 4eb643077a2a..eb01d0fb2165 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -26,6 +26,9 @@ #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) +#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET +#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) +#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) /* clang-format on */ #endif /* _SECURITY_LANDLOCK_LIMITS_H */ diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 6ff232f58618..a93bdbf52fff 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -52,12 +52,13 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) struct landlock_ruleset * landlock_create_ruleset(const access_mask_t fs_access_mask, - const access_mask_t net_access_mask) + const access_mask_t net_access_mask, + const access_mask_t scope_mask) { struct landlock_ruleset *new_ruleset; /* Informs about useless ruleset. */ - if (!fs_access_mask && !net_access_mask) + if (!fs_access_mask && !net_access_mask && !scope_mask) return ERR_PTR(-ENOMSG); new_ruleset = create_ruleset(1); if (IS_ERR(new_ruleset)) @@ -66,6 +67,8 @@ landlock_create_ruleset(const access_mask_t fs_access_mask, landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0); if (net_access_mask) landlock_add_net_access_mask(new_ruleset, net_access_mask, 0); + if (scope_mask) + landlock_add_scope_mask(new_ruleset, scope_mask, 0); return new_ruleset; } diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 0f1b5b4c8f6b..a9ac2a0487d1 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -35,6 +35,8 @@ typedef u16 access_mask_t; static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); /* Makes sure all network access rights can be stored. */ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET); +/* Makes sure all scoped rights can be stored. */ +static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE); /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */ static_assert(sizeof(unsigned long) >= sizeof(access_mask_t)); @@ -42,6 +44,7 @@ static_assert(sizeof(unsigned long) >= sizeof(access_mask_t)); struct access_masks { access_mask_t fs : LANDLOCK_NUM_ACCESS_FS; access_mask_t net : LANDLOCK_NUM_ACCESS_NET; + access_mask_t scoped : LANDLOCK_NUM_SCOPE; }; typedef u16 layer_mask_t; @@ -233,7 +236,8 @@ struct landlock_ruleset { struct landlock_ruleset * landlock_create_ruleset(const access_mask_t access_mask_fs, - const access_mask_t access_mask_net); + const access_mask_t access_mask_net, + const access_mask_t scope_mask); void landlock_put_ruleset(struct landlock_ruleset *const ruleset); void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); @@ -280,6 +284,17 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset, ruleset->access_masks[layer_level].net |= net_mask; } +static inline void +landlock_add_scope_mask(struct landlock_ruleset *const ruleset, + const access_mask_t scope_mask, const u16 layer_level) +{ + access_mask_t scoped_mask = scope_mask & LANDLOCK_MASK_SCOPE; + + /* Should already be checked in sys_landlock_create_ruleset(). */ + WARN_ON_ONCE(scope_mask != scoped_mask); + ruleset->access_masks[layer_level].scoped |= scoped_mask; +} + static inline access_mask_t landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset, const u16 layer_level) @@ -303,6 +318,13 @@ landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset, return ruleset->access_masks[layer_level].net; } +static inline access_mask_t +landlock_get_scope_mask(const struct landlock_ruleset *const ruleset, + const u16 layer_level) +{ + return ruleset->access_masks[layer_level].scoped; +} + bool landlock_unmask_layers(const struct landlock_rule *const rule, const access_mask_t access_request, layer_mask_t (*const layer_masks)[], diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index ccc8bc6c1584..c67836841e46 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -97,8 +97,9 @@ static void build_check_abi(void) */ ruleset_size = sizeof(ruleset_attr.handled_access_fs); ruleset_size += sizeof(ruleset_attr.handled_access_net); + ruleset_size += sizeof(ruleset_attr.scoped); BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); - BUILD_BUG_ON(sizeof(ruleset_attr) != 16); + BUILD_BUG_ON(sizeof(ruleset_attr) != 24); path_beneath_size = sizeof(path_beneath_attr.allowed_access); path_beneath_size += sizeof(path_beneath_attr.parent_fd); @@ -149,7 +150,7 @@ static const struct file_operations ruleset_fops = { .write = fop_dummy_write, }; -#define LANDLOCK_ABI_VERSION 5 +#define LANDLOCK_ABI_VERSION 6 /** * sys_landlock_create_ruleset - Create a new ruleset @@ -170,8 +171,9 @@ static const struct file_operations ruleset_fops = { * Possible returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; - * - %EINVAL: unknown @flags, or unknown access, or too small @size; - * - %E2BIG or %EFAULT: @attr or @size inconsistencies; + * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size; + * - %E2BIG: @attr or @size inconsistencies; + * - %EFAULT: @attr or @size inconsistencies; * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. */ SYSCALL_DEFINE3(landlock_create_ruleset, @@ -213,9 +215,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset, LANDLOCK_MASK_ACCESS_NET) return -EINVAL; + /* Checks IPC scoping content (and 32-bits cast). */ + if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE) + return -EINVAL; + /* Checks arguments and transforms to kernel struct. */ ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, - ruleset_attr.handled_access_net); + ruleset_attr.handled_access_net, + ruleset_attr.scoped); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); diff --git a/security/landlock/task.c b/security/landlock/task.c index 849f5123610b..b9390445d242 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include "common.h" #include "cred.h" @@ -108,9 +110,143 @@ 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_SCOPED_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[0].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-sandboxed processes */ + 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; + + if (is_abstract_socket(other->sk)) { + /* + * Checks if this datagram socket was already allowed to + * be connected to other. + */ + if (unix_peer(sock->sk) == other->sk) + return 0; + + if (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) diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index 3b26bf3cf5b9..1bc16fde2e8a 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -76,7 +76,7 @@ TEST(abi_version) const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(5, landlock_create_ruleset(NULL, 0, + ASSERT_EQ(6, landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, From patchwork Thu Sep 5 00:13:56 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tahera Fahimi X-Patchwork-Id: 13791550 Received: from mail-pf1-f169.google.com (mail-pf1-f169.google.com [209.85.210.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B63C728EB; Thu, 5 Sep 2024 00:14:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495265; cv=none; b=RQ4lfdXmfmyk0uKVG/UGTT3GAaMx8QvrmiN9ozblzggoIlz8KUJNXeLTUZwHciu6dwDSR6hNHVt0QTfxbXjfOliqqishQSVNYjGJIzOnfrmh8XeKibv71uPbS7aJwv8eqmQryp5R2bEOLRPYZTbXgjR/LG4Ewp/7BA5UeVSl1MA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495265; c=relaxed/simple; bh=Ph/b40sS3y4vA24Edlt58Zs6hPW4jW+bVh2lWBLifRM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=h6/Tsm1R7ZzXcIFu6NwdKtXVUCIYgnuQ6J9OH3HrIsyRyXd2fH7akl+vOVAgJ9jOdK/2kxt5v0fRg4YiclWxDFB6xBGyvtc7Lwu2K5nFsiVD8dJdY398WO+yeAmX/fo7fl7EEzFU8p7V00wJEvPRISsNoyxUnA/xo0M8R1GSFpE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Tsfl+XKi; arc=none smtp.client-ip=209.85.210.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Tsfl+XKi" Received: by mail-pf1-f169.google.com with SMTP id d2e1a72fcca58-717594b4ce7so174603b3a.0; Wed, 04 Sep 2024 17:14:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725495263; x=1726100063; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=m9Qxv0MhIUJqf7Mc01izOPbAr1VqnmVdpgZRy2Cr4ZI=; b=Tsfl+XKikd42pREfw/Qs2p3Y6bX4/IZ5flT04GvtnXZeR3rZvBzY4GhY910hEafta0 gTjlI27HnL9ag0ovb8I1nRBqReVCQM5OdEgFF7kwNfN5BTJixocXkWmsgqyz6EXpEMUU 0yfDA0aqGbekiAKLO6ZgNJU0TFERhPAclBnhw2bw1e7I84CMMaONcSgynkt63wRWWnC/ 6qSpIhqz7fMzgPJvOBLp5ni7YAjf5m03e2nv9FlcNylfdf1SNRFwhqQ7YZq9tU1CrE+v lBPlG3HELF7IjX2hYW3WgAdbYyrlYonJVjHdryQW9txc7mRnWxkSghLM47AZjetDDyvh 0LuA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725495263; x=1726100063; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=m9Qxv0MhIUJqf7Mc01izOPbAr1VqnmVdpgZRy2Cr4ZI=; b=VfnTXn1yrr/lZii+IaxCR5c/ynLzZ364Cb96ndi911EVa5iMxY0NEt+QBAnHf+ULjm NKHbZxu6Nue99ZwBl7Y+GSaAJoF5e5kvEAyD0qZGyGQEceqT13bu5sd2O03SqAU9GGGE PujLLnihn7sx5vqcF6upUtkU+EcJqbfco5Q4uQ8kGXe+SbtXHQ9doIm2lOdaw4xi/hqR 5ZVg2UuS88+yYzyA2X28tiTA9eqq2S4stqxvtRvO3doCrqyVTxUd0b+pj6a9T86r8g3w u04WLX/t2oPGxTmzZRiiqIW7wZcK4By9HR0jup8qwKe9Knr92BEdnDx2UusmmaCj9NTM dCJA== X-Forwarded-Encrypted: i=1; AJvYcCVegTQfnhW0ZXGOqtVU2WNC9vm6fllJuFiT4z0OXet4Ungs/fBizNLgPXhFt79asmSd+uUbLP1ScrEgQorObyMV9XQJ/EGO@vger.kernel.org, AJvYcCWrv5AbqLBgWkeK8Uxke7C0KJMvoxV6rPQPkUIY9BsbvQcyhEkf0lenFKMGwAVLZ8f+XtGsAknnVNBwhVE=@vger.kernel.org, AJvYcCXxaikxPOPzwuj5mtTeemvMNd40/kQqXTh5RE/uDn4Yu0Aknw8/Dgt/CiFFewsLUa6lrkcHR54P@vger.kernel.org X-Gm-Message-State: AOJu0YzPBlY1mH+CLhyWOWmWRC7U20XHMOqYBiQ/JxNHUiU/XKLMDIW8 3fgaBxK760xFPgtIQj/oPDIIkP7rZVqJxvsrmkEWx93kexwjcJts3tQjTheb X-Google-Smtp-Source: AGHT+IFTk2+yaksGZJTPn3s9d2vWrrUKU5MF3evxgeD+PJnTeRY/a6zvlgXjEuhIQl+mqfGqMYLOPA== X-Received: by 2002:a05:6a00:2193:b0:70d:ca45:a004 with SMTP id d2e1a72fcca58-715dfb68c18mr26407717b3a.13.1725495263022; Wed, 04 Sep 2024 17:14:23 -0700 (PDT) Received: from tahera-OptiPlex-5000.tail3bf47f.ts.net ([136.159.49.123]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71778534921sm2159781b3a.76.2024.09.04.17.14.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Sep 2024 17:14:22 -0700 (PDT) From: Tahera Fahimi To: outreachy@lists.linux.dev Cc: mic@digikod.net, gnoack@google.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, bjorn3_gh@protonmail.com, jannh@google.com, netdev@vger.kernel.org, Tahera Fahimi Subject: [PATCH v11 2/8] selftests/landlock: Add test for handling unknown scope Date: Wed, 4 Sep 2024 18:13:56 -0600 Message-Id: <74b363aaa7ddf80e1e5e132ce3d550a3a8bbf6da.1725494372.git.fahimitahera@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The test function, "ruleset_with_unknown_scope", is designed to validate the behaviour of the "landlock_create_ruleset" function when it is provided with an unsupported or unknown scope mask. Signed-off-by: Tahera Fahimi --- Changes in versions: v11: * Change commit subject and apply coding style --- .../testing/selftests/landlock/scoped_test.c | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tools/testing/selftests/landlock/scoped_test.c diff --git a/tools/testing/selftests/landlock/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c new file mode 100644 index 000000000000..36d7266de9dc --- /dev/null +++ b/tools/testing/selftests/landlock/scoped_test.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Landlock tests - Common scope restriction + * + * Copyright © 2024 Tahera Fahimi + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include "common.h" + +#define ACCESS_LAST LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET + +TEST(ruleset_with_unknown_scope) +{ + __u64 scoped_mask; + + for (scoped_mask = 1ULL << 63; scoped_mask != ACCESS_LAST; + scoped_mask >>= 1) { + struct landlock_ruleset_attr ruleset_attr = { + .scoped = scoped_mask, + }; + + ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0)); + ASSERT_EQ(EINVAL, errno); + } +} + +TEST_HARNESS_MAIN From patchwork Thu Sep 5 00:13:57 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tahera Fahimi X-Patchwork-Id: 13791551 Received: from mail-oa1-f41.google.com (mail-oa1-f41.google.com [209.85.160.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C3BBCA937; Thu, 5 Sep 2024 00:14:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495268; cv=none; b=RlK+GF6GB/Axi/Kro3Dg203AX0e1q+RkAnQmmKiiZaVkjRxj65r6RdUULge6mx1srzQhW9+VJwcnR0U10pqUYocVwg9vXsGKe6ZQIxPT0iVoLdRC/6Rd8t5S605Ns3X9fU13x4LaBE6aUdzvgrOgV20MVOv/gCN04bG2eL73rK4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495268; c=relaxed/simple; bh=mX/XFwG5C0TWxDzQzjcUkx4xXAoOllG0ZWKYJVoEexw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=awuKIn8Ll22rBLB3KND5EB0sqHEXGKGccf+yJp/KCT0dL5ZjTbIF6y961BK2Yxck9CfwwD01Z1vfUuTmw6Dvh4cPLrZeXjDfDIMIbPPRweUXeZ9ySUihdoroMP4ASd7+AK8pDLVAvmv7TqFZr7DJgGW4ss6wMQOf99j5gcJ8Hm8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=JivoCiDG; arc=none smtp.client-ip=209.85.160.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="JivoCiDG" Received: by mail-oa1-f41.google.com with SMTP id 586e51a60fabf-277e6002b7dso159557fac.1; Wed, 04 Sep 2024 17:14:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725495265; x=1726100065; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2BI6zKDtG2HG0fCmXFOHBI+rStZOJcwEmwBnfw9wUHQ=; b=JivoCiDG9bpI5Vz+uFAsNHfg5Hw4rE5q7TlHcVftyMmwUefutTQgfGiRiHDFZaQznI KDe2JMZOJBRtV77nZgfHGzgkhcG4yVWyd6eORsjAAwxGL5gMu/T1X511KxNPU1Z+ZTwD HywGw6fvd1xTdh78ErABkfzcsLaeCuI1W6zkjwk25bmNoKoV/MVcFAW3W2UKUyZMTi2A bHyvUBxNXz6zJaPxOjEx2RkiUdzEZSXJ4Iu/jQIECF0PZaBQmQ5UqpzI8Hi3ntirfRIn jZuzvQ3c2ntu0RPJisXIZeG+esfMWX03pkowCWEoFp6V7YQ/mswxy08IFgfBF1X2CMQ/ J0OA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725495265; x=1726100065; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=2BI6zKDtG2HG0fCmXFOHBI+rStZOJcwEmwBnfw9wUHQ=; b=k0BttL4A12p458qUdE/hm3H/M7e0/9SSLySAHNobC2arfxY6XRGChcbPug8PjDNNo1 xcURoFJpiD9X886CaycszKcigclH9Pw3XzxOGpi6hkqNK9RJZOTbQFszLoNYOTrgLDTe XiOtBwJct0r8WtkJznIgCKZ1VSavN/vL7Huux2XbC8WcEAXPxpCSf9gGrZhw4SWOssh7 H+diB8ShKhGRRLLBmE2M5QuqBo12cJ/2pkCyKoPFmgLf0RBcC2FTrS7NSCKpOn6uRygo y24F3C1y8M992e/sRiYpTHaz6CF0GQtpcIOrWxo0c65MSnzm6418QibgmPbZh1+3Rvzy G9dQ== X-Forwarded-Encrypted: i=1; AJvYcCVasPa24fFqLJW7lxF1kRY/2jbMFj5pMXWwry+9e1zbK8ntU3TaNgDChExDtUuqeYL35VmxqwlV@vger.kernel.org, AJvYcCWIUJHZpENVA3+NdTTgfYLpl/bp/H3OqUcrOV2TMGk+jJVs7s3hPVlsbG62bhVfGxxWZvtBHutsIUfuSuo=@vger.kernel.org, AJvYcCXyRc6uM2zmrOgip3uAX7SFzQSIfUvPRMRebbbnGiChh+yB9GQIE3hFiOGH8H29LeDAuhhuziVTjLsYrUa632uwpj9esT3G@vger.kernel.org X-Gm-Message-State: AOJu0Yw+idqkBkJisRrlX/QE8GL7gZTxosiJue7/ajTP35NozXg5lW2B OQ6lAvJcP96LbbfjYx2NZGVsGiCCm3qDE9hYc9B6qq1upWGOPspB X-Google-Smtp-Source: AGHT+IGRPHDFPaOyrRiWUwZOdFRWwBx7Zh9NjqmLP05p400wI5Lohx/pu+ufexDnKiFQGtV4iDhQQQ== X-Received: by 2002:a05:6871:4910:b0:277:d17b:50b4 with SMTP id 586e51a60fabf-277d17be466mr11597577fac.5.1725495264414; Wed, 04 Sep 2024 17:14:24 -0700 (PDT) Received: from tahera-OptiPlex-5000.tail3bf47f.ts.net ([136.159.49.123]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71778534921sm2159781b3a.76.2024.09.04.17.14.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Sep 2024 17:14:23 -0700 (PDT) From: Tahera Fahimi To: outreachy@lists.linux.dev Cc: mic@digikod.net, gnoack@google.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, bjorn3_gh@protonmail.com, jannh@google.com, netdev@vger.kernel.org, Tahera Fahimi Subject: [PATCH v11 3/8] selftests/landlock: Add abstract UNIX socket restriction tests Date: Wed, 4 Sep 2024 18:13:57 -0600 Message-Id: <9321c3d3bcd9212ceb4b50693e29349f8d625e16.1725494372.git.fahimitahera@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The patch introduces Landlock ABI version 6 and adds three tests that examines different scenarios for abstract UNIX socket: 1) unix_socket: base tests of the abstract socket scoping mechanism for a landlocked process, same as the ptrace test. 2) scoped_vs_unscoped_sockets: generates three processes with different domains and tests if a process with a non-scoped domain can connect to other processes. 3) outside_socket: since the socket's creator credentials are used for scoping sockets, this test examines the cases where the socket's credentials are different from the process using it. Signed-off-by: Tahera Fahimi --- Changes in versions: v11: - Adding a generalized domain creation function, "create_scoped_domain", for different types of scoped domains in "scoped_common.h" file. - Rename "unix_socket" to "scoped_domain" and moving its variants to "scoped_base_variants.h" file (for future use in other IPC tests). - Minor code improvement, and renaming variables. - Rename "optional_scoping" to "scoped_vs_unscoped_sockets", and add support for datagram and stream sockets. Moving its variants to "scoped_multiple_domain_variants.h" file. v10: - Code improvements by changing fixture variables to local ones. - Rename "unix_sock_special_cases" to "outside_socket" v9: - Move pathname_address_sockets to a different patch. - Extend optional_scoping test scenarios. - Removing hardcoded numbers and using "backlog" instead. V8: - Move tests to scoped_abstract_unix_test.c file. - To avoid potential conflicts among Unix socket names in different tests, set_unix_address is added to common.h to set different sun_path for Unix sockets. - protocol_variant and service_fixture structures are also moved to common.h - Adding pathname_address_sockets to cover all types of address formats for unix sockets, and moving remove_path() to common.h to reuse in this test. V7: - Introducing landlock ABI version 6. - Adding some edge test cases to optional_scoping test. - Using `enum` for different domains in optional_scoping tests. - Extend unix_sock_special_cases test cases for connected(SOCK_STREAM) sockets. - Modifying inline comments. V6: - Introducing optional_scoping test which ensures a sandboxed process with a non-scoped domain can still connect to another abstract unix socket(either sandboxed or non-sandboxed). - Introducing unix_sock_special_cases test which tests examines scenarios where the connecting sockets have different domain than the process using them. V4: - Introducing unix_socket to evaluate the basic scoping mechanism for abstract unix sockets. --- tools/testing/selftests/landlock/common.h | 38 ++ tools/testing/selftests/landlock/net_test.c | 31 +- .../landlock/scoped_abstract_unix_test.c | 620 ++++++++++++++++++ .../selftests/landlock/scoped_base_variants.h | 154 +++++ .../selftests/landlock/scoped_common.h | 28 + .../scoped_multiple_domain_variants.h | 154 +++++ 6 files changed, 995 insertions(+), 30 deletions(-) create mode 100644 tools/testing/selftests/landlock/scoped_abstract_unix_test.c create mode 100644 tools/testing/selftests/landlock/scoped_base_variants.h create mode 100644 tools/testing/selftests/landlock/scoped_common.h create mode 100644 tools/testing/selftests/landlock/scoped_multiple_domain_variants.h diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index 7e2b431b9f90..cca387df86c2 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -7,6 +7,7 @@ * Copyright © 2021 Microsoft Corporation */ +#include #include #include #include @@ -14,10 +15,12 @@ #include #include #include +#include #include #include #include "../kselftest_harness.h" +#define TMP_DIR "tmp" #ifndef __maybe_unused #define __maybe_unused __attribute__((__unused__)) @@ -226,3 +229,38 @@ enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd) TH_LOG("Failed to enforce ruleset: %s", strerror(errno)); } } + +struct protocol_variant { + int domain; + int type; +}; + +struct service_fixture { + struct protocol_variant protocol; + /* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */ + unsigned short port; + union { + struct sockaddr_in ipv4_addr; + struct sockaddr_in6 ipv6_addr; + struct { + struct sockaddr_un unix_addr; + socklen_t unix_addr_len; + }; + }; +}; + +static pid_t __maybe_unused sys_gettid(void) +{ + return syscall(__NR_gettid); +} + +static void __maybe_unused set_unix_address(struct service_fixture *const srv, + const unsigned short index) +{ + srv->unix_addr.sun_family = AF_UNIX; + sprintf(srv->unix_addr.sun_path, + "_selftests-landlock-abstract-unix-tid%d-index%d", sys_gettid(), + index); + srv->unix_addr_len = SUN_LEN(&srv->unix_addr); + srv->unix_addr.sun_path[0] = '\0'; +} diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index f21cfbbc3638..4e0aeb53b225 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -36,30 +36,6 @@ enum sandbox_type { TCP_SANDBOX, }; -struct protocol_variant { - int domain; - int type; -}; - -struct service_fixture { - struct protocol_variant protocol; - /* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */ - unsigned short port; - union { - struct sockaddr_in ipv4_addr; - struct sockaddr_in6 ipv6_addr; - struct { - struct sockaddr_un unix_addr; - socklen_t unix_addr_len; - }; - }; -}; - -static pid_t sys_gettid(void) -{ - return syscall(__NR_gettid); -} - static int set_service(struct service_fixture *const srv, const struct protocol_variant prot, const unsigned short index) @@ -92,12 +68,7 @@ static int set_service(struct service_fixture *const srv, return 0; case AF_UNIX: - srv->unix_addr.sun_family = prot.domain; - sprintf(srv->unix_addr.sun_path, - "_selftests-landlock-net-tid%d-index%d", sys_gettid(), - index); - srv->unix_addr_len = SUN_LEN(&srv->unix_addr); - srv->unix_addr.sun_path[0] = '\0'; + set_unix_address(srv, index); return 0; } return 1; diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c new file mode 100644 index 000000000000..00ea5151979f --- /dev/null +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Landlock tests - Abstract Unix Socket + * + * Copyright © 2024 Tahera Fahimi + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "scoped_common.h" + +/* Number pending connections queue to be hold. */ +const short backlog = 10; + +static void create_fs_domain(struct __test_metadata *const _metadata) +{ + int ruleset_fd; + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, + }; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + EXPECT_LE(0, ruleset_fd) + { + TH_LOG("Failed to create a ruleset: %s", strerror(errno)); + } + EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); + EXPECT_EQ(0, close(ruleset_fd)); +} + +/* clang-format off */ +FIXTURE(scoped_domains) +{ + struct service_fixture stream_address, dgram_address; +}; + +#include "scoped_base_variants.h" + +/* clang-format on */ +FIXTURE_SETUP(scoped_domains) +{ + memset(&self->stream_address, 0, sizeof(self->stream_address)); + memset(&self->dgram_address, 0, sizeof(self->dgram_address)); + set_unix_address(&self->stream_address, 0); + set_unix_address(&self->dgram_address, 1); +} + +FIXTURE_TEARDOWN(scoped_domains) +{ +} + +/* + * Test unix_stream_connect() and unix_may_send() for a child connecting to its parent, + * when they have scoped domain or no domain. + */ +TEST_F(scoped_domains, connect_to_parent) +{ + pid_t child; + bool can_connect_to_parent; + int err, err_dgram, status; + int pipe_parent[2]; + int stream_socket, dgram_socket; + + drop_caps(_metadata); + /* + * can_connect_to_parent is true if a child process can connect to its + * parent process. This depends on the child process is not isolated from + * the parent with a dedicated Landlock domain. + */ + can_connect_to_parent = !variant->domain_child; + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + if (variant->domain_both) { + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + if (!__test_passed(_metadata)) + return; + } + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + char buf_child; + + ASSERT_EQ(0, close(pipe_parent[1])); + if (variant->domain_child) + create_scoped_domain( + _metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + stream_socket = socket(AF_UNIX, SOCK_STREAM, 0); + dgram_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + + ASSERT_NE(-1, stream_socket); + ASSERT_NE(-1, dgram_socket); + + /* wait for the server */ + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); + + err = connect(stream_socket, &self->stream_address.unix_addr, + (self->stream_address).unix_addr_len); + err_dgram = connect(dgram_socket, + &self->dgram_address.unix_addr, + (self->dgram_address).unix_addr_len); + if (can_connect_to_parent) { + EXPECT_EQ(0, err); + EXPECT_EQ(0, err_dgram); + } else { + EXPECT_EQ(-1, err); + EXPECT_EQ(-1, err_dgram); + EXPECT_EQ(EPERM, errno); + } + ASSERT_EQ(0, close(stream_socket)); + ASSERT_EQ(0, close(dgram_socket)); + _exit(_metadata->exit_code); + return; + } + ASSERT_EQ(0, close(pipe_parent[0])); + if (variant->domain_parent) + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + stream_socket = socket(AF_UNIX, SOCK_STREAM, 0); + dgram_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, stream_socket); + ASSERT_NE(-1, dgram_socket); + ASSERT_EQ(0, bind(stream_socket, &self->stream_address.unix_addr, + (self->stream_address).unix_addr_len)); + ASSERT_EQ(0, bind(dgram_socket, &self->dgram_address.unix_addr, + (self->dgram_address).unix_addr_len)); + ASSERT_EQ(0, listen(stream_socket, backlog)); + + /* signal to child that parent is listening */ + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(0, close(stream_socket)); + ASSERT_EQ(0, close(dgram_socket)); + + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + +/* + * Test unix_stream_connect() and unix_may_send() for a parent connecting to its child, + * when they have scoped domain or no domain. + */ +TEST_F(scoped_domains, connect_to_child) +{ + pid_t child; + bool can_connect_to_child; + int err, err_dgram, status; + int pipe_child[2], pipe_parent[2]; + char buf; + int stream_socket, dgram_socket; + + drop_caps(_metadata); + /* + * can_connect_to_child is true if a parent process can connect to its + * child process. The parent process is not isolated from the child + * with a dedicated Landlock domain. + */ + can_connect_to_child = !variant->domain_parent; + + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + if (variant->domain_both) { + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + if (!__test_passed(_metadata)) + return; + } + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + ASSERT_EQ(0, close(pipe_parent[1])); + ASSERT_EQ(0, close(pipe_child[0])); + if (variant->domain_child) + create_scoped_domain( + _metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + /* Waits for the parent to be in a domain, if any. */ + ASSERT_EQ(1, read(pipe_parent[0], &buf, 1)); + + stream_socket = socket(AF_UNIX, SOCK_STREAM, 0); + dgram_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, stream_socket); + ASSERT_NE(-1, dgram_socket); + ASSERT_EQ(0, + bind(stream_socket, &self->stream_address.unix_addr, + (self->stream_address).unix_addr_len)); + ASSERT_EQ(0, bind(dgram_socket, &self->dgram_address.unix_addr, + (self->dgram_address).unix_addr_len)); + ASSERT_EQ(0, listen(stream_socket, backlog)); + /* signal to parent that child is listening */ + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); + /* wait to connect */ + ASSERT_EQ(1, read(pipe_parent[0], &buf, 1)); + ASSERT_EQ(0, close(stream_socket)); + ASSERT_EQ(0, close(dgram_socket)); + _exit(_metadata->exit_code); + return; + } + ASSERT_EQ(0, close(pipe_child[1])); + ASSERT_EQ(0, close(pipe_parent[0])); + + if (variant->domain_parent) + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + /* Signals that the parent is in a domain, if any. */ + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + + stream_socket = socket(AF_UNIX, SOCK_STREAM, 0); + dgram_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, stream_socket); + ASSERT_NE(-1, dgram_socket); + + /* Waits for the child to listen */ + ASSERT_EQ(1, read(pipe_child[0], &buf, 1)); + err = connect(stream_socket, &self->stream_address.unix_addr, + (self->stream_address).unix_addr_len); + err_dgram = connect(dgram_socket, &self->dgram_address.unix_addr, + (self->dgram_address).unix_addr_len); + if (can_connect_to_child) { + EXPECT_EQ(0, err); + EXPECT_EQ(0, err_dgram); + } else { + EXPECT_EQ(-1, err); + EXPECT_EQ(-1, err_dgram); + EXPECT_EQ(EPERM, errno); + } + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + ASSERT_EQ(0, close(stream_socket)); + ASSERT_EQ(0, close(dgram_socket)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + +/* clang-format off */ +FIXTURE(scoped_vs_unscoped_sockets) +{ + struct service_fixture parent_stream_address, parent_dgram_address, + child_stream_address, child_dgram_address; +}; + +#include "scoped_multiple_domain_variants.h" +/* clang-format on */ + +FIXTURE_SETUP(scoped_vs_unscoped_sockets) +{ + memset(&self->parent_stream_address, 0, + sizeof(self->parent_stream_address)); + set_unix_address(&self->parent_stream_address, 0); + memset(&self->parent_dgram_address, 0, + sizeof(self->parent_dgram_address)); + set_unix_address(&self->parent_dgram_address, 1); + memset(&self->child_stream_address, 0, + sizeof(self->child_stream_address)); + set_unix_address(&self->child_stream_address, 2); + memset(&self->child_dgram_address, 0, + sizeof(self->child_dgram_address)); + set_unix_address(&self->child_dgram_address, 3); +} + +FIXTURE_TEARDOWN(scoped_vs_unscoped_sockets) +{ +} + +/* + * Test unix_stream_connect and unix_may_send for parent, child and + * grand child processes when they can have scoped or non-scoped domains. + */ +TEST_F(scoped_vs_unscoped_sockets, unix_scoping) +{ + pid_t child; + int status; + bool can_connect_to_parent, can_connect_to_child; + int pipe_parent[2]; + int stream_server, dgram_server; + + drop_caps(_metadata); + can_connect_to_child = (variant->domain_grand_child != SCOPE_SANDBOX); + can_connect_to_parent = (can_connect_to_child && + (variant->domain_children != SCOPE_SANDBOX)); + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + + if (variant->domain_all == OTHER_SANDBOX) + create_fs_domain(_metadata); + else if (variant->domain_all == SCOPE_SANDBOX) + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int pipe_child[2]; + pid_t grand_child; + + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); + + if (variant->domain_children == OTHER_SANDBOX) + create_fs_domain(_metadata); + else if (variant->domain_children == SCOPE_SANDBOX) + create_scoped_domain( + _metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + grand_child = fork(); + ASSERT_LE(0, grand_child); + if (grand_child == 0) { + char buf; + int err, dgram_err; + int stream_client, dgram_client; + + ASSERT_EQ(0, close(pipe_parent[1])); + ASSERT_EQ(0, close(pipe_child[1])); + + if (variant->domain_grand_child == OTHER_SANDBOX) + create_fs_domain(_metadata); + else if (variant->domain_grand_child == SCOPE_SANDBOX) + create_scoped_domain( + _metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + stream_client = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_NE(-1, stream_client); + dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, dgram_client); + + ASSERT_EQ(1, read(pipe_child[0], &buf, 1)); + err = connect(stream_client, + &self->child_stream_address.unix_addr, + self->child_stream_address.unix_addr_len); + dgram_err = connect( + dgram_client, + &self->child_dgram_address.unix_addr, + self->child_dgram_address.unix_addr_len); + if (can_connect_to_child) { + EXPECT_EQ(0, err); + EXPECT_EQ(0, dgram_err); + } else { + EXPECT_EQ(-1, err); + EXPECT_EQ(-1, dgram_err); + EXPECT_EQ(EPERM, errno); + } + + EXPECT_EQ(0, close(stream_client)); + stream_client = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_NE(-1, stream_client); + + ASSERT_EQ(1, read(pipe_parent[0], &buf, 1)); + err = connect( + stream_client, + &self->parent_stream_address.unix_addr, + self->parent_stream_address.unix_addr_len); + dgram_err = connect( + dgram_client, + &self->parent_dgram_address.unix_addr, + self->parent_dgram_address.unix_addr_len); + + if (can_connect_to_parent) { + EXPECT_EQ(0, err); + EXPECT_EQ(0, dgram_err); + } else { + EXPECT_EQ(-1, err); + EXPECT_EQ(-1, dgram_err); + EXPECT_EQ(EPERM, errno); + } + EXPECT_EQ(0, close(stream_client)); + EXPECT_EQ(0, close(dgram_client)); + + _exit(_metadata->exit_code); + return; + } + ASSERT_EQ(0, close(pipe_child[0])); + if (variant->domain_child == OTHER_SANDBOX) + create_fs_domain(_metadata); + else if (variant->domain_child == SCOPE_SANDBOX) + create_scoped_domain( + _metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + stream_server = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_NE(-1, stream_server); + dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, dgram_server); + + ASSERT_EQ(0, bind(stream_server, + &self->child_stream_address.unix_addr, + self->child_stream_address.unix_addr_len)); + ASSERT_EQ(0, bind(dgram_server, + &self->child_dgram_address.unix_addr, + self->child_dgram_address.unix_addr_len)); + ASSERT_EQ(0, listen(stream_server, backlog)); + + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); + ASSERT_EQ(grand_child, waitpid(grand_child, &status, 0)); + ASSERT_EQ(0, close(stream_server)) + ASSERT_EQ(0, close(dgram_server)); + return; + } + ASSERT_EQ(0, close(pipe_parent[0])); + + if (variant->domain_parent == OTHER_SANDBOX) + create_fs_domain(_metadata); + else if (variant->domain_parent == SCOPE_SANDBOX) + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + stream_server = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_NE(-1, stream_server); + dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, dgram_server); + ASSERT_EQ(0, bind(stream_server, &self->parent_stream_address.unix_addr, + self->parent_stream_address.unix_addr_len)); + ASSERT_EQ(0, bind(dgram_server, &self->parent_dgram_address.unix_addr, + self->parent_dgram_address.unix_addr_len)); + + ASSERT_EQ(0, listen(stream_server, backlog)); + + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(0, close(stream_server)); + ASSERT_EQ(0, close(dgram_server)); + + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + +/* clang-format off */ +FIXTURE(outside_socket) +{ + struct service_fixture address, transit_address; +}; +/* clang-format on */ + +FIXTURE_VARIANT(outside_socket) +{ + const bool domain_server; + const bool domain_server_socket; + const int type; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(outside_socket, allow_dgram_server_sock_domain) { + /* clang-format on */ + .domain_server = false, + .domain_server_socket = true, + .type = SOCK_DGRAM, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(outside_socket, deny_dgram_server_domain) { + /* clang-format on */ + .domain_server = true, + .domain_server_socket = false, + .type = SOCK_DGRAM, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(outside_socket, allow_stream_server_sock_domain) { + /* clang-format on */ + .domain_server = false, + .domain_server_socket = true, + .type = SOCK_STREAM, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(outside_socket, deny_stream_server_domain) { + /* clang-format on */ + .domain_server = true, + .domain_server_socket = false, + .type = SOCK_STREAM, +}; + +FIXTURE_SETUP(outside_socket) +{ + memset(&self->transit_address, 0, sizeof(self->transit_address)); + set_unix_address(&self->transit_address, 0); + memset(&self->address, 0, sizeof(self->address)); + set_unix_address(&self->address, 1); +} + +FIXTURE_TEARDOWN(outside_socket) +{ +} + +/* + * Test unix_stream_connect and unix_may_send for parent and child processes + * when connecting socket has different domain than the process using it. + */ +TEST_F(outside_socket, socket_with_different_domain) +{ + pid_t child; + int err, status; + int pipe_child[2], pipe_parent[2]; + char buf_parent; + int sock; + + drop_caps(_metadata); + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + char buf_child; + + ASSERT_EQ(0, close(pipe_parent[1])); + ASSERT_EQ(0, close(pipe_child[0])); + + /* client always has domain */ + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + if (variant->domain_server_socket) { + int data_socket, stream_server; + int fd_sock = socket(AF_UNIX, variant->type, 0); + + ASSERT_NE(-1, fd_sock); + + stream_server = socket(AF_UNIX, SOCK_STREAM, 0); + + ASSERT_NE(-1, stream_server); + ASSERT_EQ(0, bind(stream_server, + &self->transit_address.unix_addr, + self->transit_address.unix_addr_len)); + ASSERT_EQ(0, listen(stream_server, backlog)); + + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); + + data_socket = accept(stream_server, NULL, NULL); + + ASSERT_EQ(0, send_fd(data_socket, fd_sock)); + ASSERT_EQ(0, close(fd_sock)); + ASSERT_EQ(0, close(stream_server)); + } + + sock = socket(AF_UNIX, variant->type, 0); + ASSERT_NE(-1, sock); + /* wait for parent signal for connection */ + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); + + err = connect(sock, &self->address.unix_addr, + self->address.unix_addr_len); + if (!variant->domain_server_socket) { + EXPECT_EQ(-1, err); + EXPECT_EQ(EPERM, errno); + } else { + EXPECT_EQ(0, err); + } + ASSERT_EQ(0, close(sock)); + _exit(_metadata->exit_code); + return; + } + ASSERT_EQ(0, close(pipe_child[1])); + ASSERT_EQ(0, close(pipe_parent[0])); + + if (!variant->domain_server_socket) { + sock = socket(AF_UNIX, variant->type, 0); + } else { + int cli = socket(AF_UNIX, SOCK_STREAM, 0); + + ASSERT_NE(-1, cli); + ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)); + ASSERT_EQ(0, connect(cli, &self->transit_address.unix_addr, + self->transit_address.unix_addr_len)); + + sock = recv_fd(cli); + ASSERT_LE(0, sock); + ASSERT_EQ(0, close(cli)); + } + + ASSERT_NE(-1, sock); + + if (variant->domain_server) + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + ASSERT_EQ(0, bind(sock, &self->address.unix_addr, + self->address.unix_addr_len)); + if (variant->type == SOCK_STREAM) + ASSERT_EQ(0, listen(sock, backlog)); + /* signal to child that parent is listening */ + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(0, close(sock)); + + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/landlock/scoped_base_variants.h b/tools/testing/selftests/landlock/scoped_base_variants.h new file mode 100644 index 000000000000..a070ad4693e6 --- /dev/null +++ b/tools/testing/selftests/landlock/scoped_base_variants.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Landlock scoped_domains variants + * + * Copyright © 2024 Tahera Fahimi + */ + +#define _GNU_SOURCE + +/* clang-format on */ +FIXTURE_VARIANT(scoped_domains) +{ + bool domain_both; + bool domain_parent; + bool domain_child; +}; + +/* + * No domain + * + * P1-. P1 -> P2 : allow + * \ P2 -> P1 : allow + * 'P2 + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_domains, without_domain) { + /* clang-format on */ + .domain_both = false, + .domain_parent = false, + .domain_child = false, +}; + +/* + * Child domain + * + * P1--. P1 -> P2 : allow + * \ P2 -> P1 : deny + * .'-----. + * | P2 | + * '------' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_domains, with_child_domain) { + /* clang-format on */ + .domain_both = false, + .domain_parent = false, + .domain_child = true, +}; + +/* + * Parent domain + * .------. + * | P1 --. P1 -> P2 : deny + * '------' \ P2 -> P1 : allow + * ' + * P2 + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_domains, with_parent_domain) { + /* clang-format on */ + .domain_both = false, + .domain_parent = true, + .domain_child = false, +}; + +/* + * Parent + child domain (siblings) + * .------. + * | P1 ---. P1 -> P2 : deny + * '------' \ P2 -> P1 : deny + * .---'--. + * | P2 | + * '------' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_domains, with_sibling_domain) { + /* clang-format on */ + .domain_both = false, + .domain_parent = true, + .domain_child = true, +}; + +/* + * Same domain (inherited) + * .-------------. + * | P1----. | P1 -> P2 : allow + * | \ | P2 -> P1 : allow + * | ' | + * | P2 | + * '-------------' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_domains, inherited_domain) { + /* clang-format on */ + .domain_both = true, + .domain_parent = false, + .domain_child = false, +}; + +/* + * Inherited + child domain + * .-----------------. + * | P1----. | P1 -> P2 : allow + * | \ | P2 -> P1 : deny + * | .-'----. | + * | | P2 | | + * | '------' | + * '-----------------' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_domains, nested_domain) { + /* clang-format on */ + .domain_both = true, + .domain_parent = false, + .domain_child = true, +}; + +/* + * Inherited + parent domain + * .-----------------. + * |.------. | P1 -> P2 : deny + * || P1 ----. | P2 -> P1 : allow + * |'------' \ | + * | ' | + * | P2 | + * '-----------------' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_domains, with_nested_and_parent_domain) { + /* clang-format on */ + .domain_both = true, + .domain_parent = true, + .domain_child = false, +}; + +/* + * Inherited + parent and child domain (siblings) + * .-----------------. + * | .------. | P1 -> P2 : deny + * | | P1 . | P2 -> P1 : deny + * | '------'\ | + * | \ | + * | .--'---. | + * | | P2 | | + * | '------' | + * '-----------------' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_domains, with_forked_domains) { + /* clang-format on */ + .domain_both = true, + .domain_parent = true, + .domain_child = true, +}; diff --git a/tools/testing/selftests/landlock/scoped_common.h b/tools/testing/selftests/landlock/scoped_common.h new file mode 100644 index 000000000000..a9a912d30c4d --- /dev/null +++ b/tools/testing/selftests/landlock/scoped_common.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Landlock scope test helpers + * + * Copyright © 2024 Tahera Fahimi + */ + +#define _GNU_SOURCE + +#include + +static void create_scoped_domain(struct __test_metadata *const _metadata, + const __u16 scope) +{ + int ruleset_fd; + const struct landlock_ruleset_attr ruleset_attr = { + .scoped = scope, + }; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd) + { + TH_LOG("Failed to create a ruleset: %s", strerror(errno)); + } + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); +} diff --git a/tools/testing/selftests/landlock/scoped_multiple_domain_variants.h b/tools/testing/selftests/landlock/scoped_multiple_domain_variants.h new file mode 100644 index 000000000000..f035c9401a5f --- /dev/null +++ b/tools/testing/selftests/landlock/scoped_multiple_domain_variants.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Landlock variants for three processes with various domains. + * + * Copyright © 2024 Tahera Fahimi + */ + +#define _GNU_SOURCE + +enum sandbox_type { + NO_SANDBOX, + SCOPE_SANDBOX, + /* Any other type of sandboxing domain */ + OTHER_SANDBOX, +}; + +/* clang-format on */ +FIXTURE_VARIANT(scoped_vs_unscoped_sockets) +{ + const int domain_all; + const int domain_parent; + const int domain_children; + const int domain_child; + const int domain_grand_child; +}; + +/* + * .-----------------. + * | ####### | P3 -> P2 : allow + * | P1----# P2 # | P3 -> P1 : deny + * | # | # | + * | # P3 # | + * | ####### | + * '-----------------' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_vs_unscoped_sockets, deny_scoped) { + .domain_all = OTHER_SANDBOX, + .domain_parent = NO_SANDBOX, + .domain_children = SCOPE_SANDBOX, + .domain_child = NO_SANDBOX, + .domain_grand_child = NO_SANDBOX, + /* clang-format on */ +}; + +/* + * ################### + * # ####### # P3 -> P2 : allow + * # P1----# P2 # # P3 -> P1 : deny + * # # | # # + * # # P3 # # + * # ####### # + * ################### + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_vs_unscoped_sockets, all_scoped) { + .domain_all = SCOPE_SANDBOX, + .domain_parent = NO_SANDBOX, + .domain_children = SCOPE_SANDBOX, + .domain_child = NO_SANDBOX, + .domain_grand_child = NO_SANDBOX, + /* clang-format on */ +}; + +/* + * .-----------------. + * | .-----. | P3 -> P2 : allow + * | P1----| P2 | | P3 -> P1 : allow + * | | | | + * | | P3 | | + * | '-----' | + * '-----------------' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_vs_unscoped_sockets, allow_with_other_domain) { + .domain_all = OTHER_SANDBOX, + .domain_parent = NO_SANDBOX, + .domain_children = OTHER_SANDBOX, + .domain_child = NO_SANDBOX, + .domain_grand_child = NO_SANDBOX, + /* clang-format on */ +}; + +/* + * .----. ###### P3 -> P2 : allow + * | P1 |----# P2 # P3 -> P1 : allow + * '----' ###### + * | + * P3 + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_vs_unscoped_sockets, allow_with_one_domain) { + .domain_all = NO_SANDBOX, + .domain_parent = OTHER_SANDBOX, + .domain_children = NO_SANDBOX, + .domain_child = SCOPE_SANDBOX, + .domain_grand_child = NO_SANDBOX, + /* clang-format on */ +}; + +/* + * ###### .-----. P3 -> P2 : allow + * # P1 #----| P2 | P3 -> P1 : allow + * ###### '-----' + * | + * P3 + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_vs_unscoped_sockets, allow_with_grand_parent_scoped) { + .domain_all = NO_SANDBOX, + .domain_parent = SCOPE_SANDBOX, + .domain_children = NO_SANDBOX, + .domain_child = OTHER_SANDBOX, + .domain_grand_child = NO_SANDBOX, + /* clang-format on */ +}; + +/* + * ###### ###### P3 -> P2 : allow + * # P1 #----# P2 # P3 -> P1 : allow + * ###### ###### + * | + * .----. + * | P3 | + * '----' + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_vs_unscoped_sockets, allow_with_parents_domain) { + .domain_all = NO_SANDBOX, + .domain_parent = SCOPE_SANDBOX, + .domain_children = NO_SANDBOX, + .domain_child = SCOPE_SANDBOX, + .domain_grand_child = NO_SANDBOX, + /* clang-format on */ +}; + +/* + * ###### P3 -> P2 : deny + * # P1 #----P2 P3 -> P1 : deny + * ###### | + * | + * ###### + * # P3 # + * ###### + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoped_vs_unscoped_sockets, deny_with_self_and_grandparent_domain) { + .domain_all = NO_SANDBOX, + .domain_parent = SCOPE_SANDBOX, + .domain_children = NO_SANDBOX, + .domain_child = NO_SANDBOX, + .domain_grand_child = SCOPE_SANDBOX, + /* clang-format on */ +}; From patchwork Thu Sep 5 00:13:58 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tahera Fahimi X-Patchwork-Id: 13791552 Received: from mail-ot1-f42.google.com (mail-ot1-f42.google.com [209.85.210.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B8972D515; Thu, 5 Sep 2024 00:14:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495268; cv=none; b=g5gYoC9Tk39eQ7Zr/+gfe5WSmmqsbgytukyzTX37y+5ovhl9VGWHiT4P2V/mjT3NHfe/EYyPd49C+xWN73IXk6SKRBl8TkFsvRVyEHfeRXeD+azqdBXrzqE82quPAyGiFjuIfFXykEsvlPNxYTWbiPfb3anNs3s2Xgh1JhcuzXg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495268; c=relaxed/simple; bh=6Mppb/IteTfIlLgsMNenC9CxdXvOR6R4inruYITVZ04=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=ZlPQhr8kR9gNjEHI7HJSafnHQwXpkZwtJjQmUWGSuz/+o+Uu0z/DZqrTm6IjrtvDDY+4ryXGWJwzvP/Vwk4NkYgDk3q6fa7YerD6z4I9GtN0KXFzB+gdTlZzsdr0IfMbvhXFclCBFMJB+eYnAPFsK2fau7ooNWYOYqYRviKWBrU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Q5ko+Q4H; arc=none smtp.client-ip=209.85.210.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Q5ko+Q4H" Received: by mail-ot1-f42.google.com with SMTP id 46e09a7af769-70f794cd240so118978a34.0; Wed, 04 Sep 2024 17:14:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725495266; x=1726100066; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=WdUGGmo4EZXMWUsoOjFrOLVLRkPFoKDmP9VM6UY6JiY=; b=Q5ko+Q4HBf6FnWdFTB0eFo95aJT2R7grtGU6oO7pRGEZoxjhkmfWYsMW4g6NZLBiQK D5avr2wxlqpvgar0Mxkjsgc+0naUtrsmundkEAC+urNME43gB1bnwzNmBmIsZkbNuFgk B/DEUhuXTm6IAZPj68bNYVjIMhAAOx6+XcbyGAKdrS6bjWNVqxPoqOsw8nIIJl99zqo0 MjfHOLGGHqHm0ihR+x7q3xRH76FTjBxBVR83Yg0QbpGKTTda1fftetapkMvYv74Vtjtu nOeOQNLb36BNKkX6b+EeD7Qfggq8fIhKN07T8oZO3zP+Hy2yLibp3qypY1SDPI2aXl+Q 2jBA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725495266; x=1726100066; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=WdUGGmo4EZXMWUsoOjFrOLVLRkPFoKDmP9VM6UY6JiY=; b=EeBQnNDzI15lcAJsGI07HSpKZe7bgfzyqKD1JvWghx7T6DiEDJ8SNcuLe00uJ0E+oU YVD9SSiubYeapXMxRK0uqIDQ9MiR8ZV5K1ACQANPt7EuHWYFPqHvvlz/APHA8uFeV9bf JstKOXUka3u7JhqcLmbQKN4NeBEsAINHmRo5M/+NpatX99FM7KUU+eJWLIrXx9MmFxEE d3UKd0YK9vhsLGOcHMjmWQR9lqei8okr2D0wAeWz6AquBecirv75kpdwGwRamTprdDp2 wi49UX5koETCk2V5K6w4E3bvG7yjCA08ly+fQv/DFpjfMzfskhxMpPUfX7dRUPgEJ0ft Ce4w== X-Forwarded-Encrypted: i=1; AJvYcCU8gxeW+73uzD86jSvANheBYTD8MdaK2BXUW1bzmM77XDZsH4a2QfOPMu7w5DGjQ9nWeSmdycqKqeYIGaA=@vger.kernel.org, AJvYcCUFGLU3sIhBqVypJs6GqpjuszVIU3iopajVBnBnrWSpiWE1WPPt8LxmFIFVLdrxnF82ngNZUp4Qu+jmlpGGcMp+MHfmRDEY@vger.kernel.org, AJvYcCVlEZYTOvAeIaHBGOQfPBPYTv7NrDieUC/MA9IjIpCzjn6iqCOoMrzFpWqcs9QLiBNGITWxLDrV@vger.kernel.org X-Gm-Message-State: AOJu0Yz+pMtQV8+v7QL3kFFH8/l/q6OGAgW63eln9LWvw7+qJNOl8VKW lYRXsBXm7q7TuiewV9J1m0tBX2NvH+Rh44Avolxgtnv7cSZ1mCt5E0ZLHJlu X-Google-Smtp-Source: AGHT+IFUHVZGaAJdSoHKOAWujxHJjsI4Egi6h0piPVafcBQ2aa228X4CzWAN/Tv7siQ9XqpsZqQ7BQ== X-Received: by 2002:a05:6358:7e50:b0:1b1:a8a6:1630 with SMTP id e5c5f4694b2df-1b7edbcc8bfmr2225984755d.20.1725495265851; Wed, 04 Sep 2024 17:14:25 -0700 (PDT) Received: from tahera-OptiPlex-5000.tail3bf47f.ts.net ([136.159.49.123]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71778534921sm2159781b3a.76.2024.09.04.17.14.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Sep 2024 17:14:25 -0700 (PDT) From: Tahera Fahimi To: outreachy@lists.linux.dev Cc: mic@digikod.net, gnoack@google.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, bjorn3_gh@protonmail.com, jannh@google.com, netdev@vger.kernel.org, Tahera Fahimi Subject: [PATCH v11 4/8] selftests/landlock: Add tests for UNIX sockets with any address formats Date: Wed, 4 Sep 2024 18:13:58 -0600 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This patch expands abstract UNIX socket restriction tests by examining different scenarios for UNIX sockets with pathname or unnamed address formats connection with scoped domain. The test "various_address_sockets" ensures that UNIX sockets bound to a filesystem pathname and unnamed sockets created by socketpair can still connect to a socket outside of their scoped domain, meaning that even if the domain is scoped with LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET, the socket can connect to a socket outside the scoped domain. Signed-off-by: Tahera Fahimi --- changes in versions: v11: - Using generalized scoped domain creation, "create_scoped_domain" - Rename pathname_address_sockets to various_address_sockets - Using local variables. - Commit improvement. - Support test for unnamed datagram sockets(via socketpair(2)). v10: - Code improvements by changing fixture variables to local ones. - Commit improvement. v9: - Moving remove_path() back to fs_test.c, and using unlink(2) and rmdir(2) instead. - Removing hard-coded numbers and using "backlog" instead. V8: - Adding pathname_address_sockets to cover all types of address formats for unix sockets, and moving remove_path() to common.h to reuse in this test. --- .../landlock/scoped_abstract_unix_test.c | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c index 00ea5151979f..8fc47e45d17e 100644 --- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c @@ -617,4 +617,206 @@ TEST_F(outside_socket, socket_with_different_domain) _metadata->exit_code = KSFT_FAIL; } +static const char path1[] = TMP_DIR "/s1_variant1"; +static const char path2[] = TMP_DIR "/s2_variant1"; + +/* clang-format off */ +FIXTURE(various_address_sockets) +{ + struct service_fixture stream_address, dgram_address; +}; +/* clang-format on */ + +FIXTURE_VARIANT(various_address_sockets) +{ + const int domain; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_scoped_domain) { + /* clang-format on */ + .domain = SCOPE_SANDBOX, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_other_domain) { + /* clang-format on */ + .domain = OTHER_SANDBOX, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(various_address_sockets, pathname_socket_no_domain) { + /* clang-format on */ + .domain = NO_SANDBOX, +}; + +FIXTURE_SETUP(various_address_sockets) +{ + disable_caps(_metadata); + umask(0077); + ASSERT_EQ(0, mkdir(TMP_DIR, 0700)); + + ASSERT_EQ(0, mknod(path1, S_IFREG | 0700, 0)) + { + TH_LOG("Failed to create file \"%s\": %s", path1, + strerror(errno)); + ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR)); + } + ASSERT_EQ(0, mknod(path2, S_IFREG | 0700, 0)) + { + TH_LOG("Failed to create file \"%s\": %s", path2, + strerror(errno)); + ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR)); + } + memset(&self->stream_address, 0, sizeof(self->stream_address)); + set_unix_address(&self->stream_address, 0); + memset(&self->dgram_address, 0, sizeof(self->dgram_address)); + set_unix_address(&self->dgram_address, 1); +} + +FIXTURE_TEARDOWN(various_address_sockets) +{ + ASSERT_EQ(0, unlink(path1) & rmdir(path1)); + ASSERT_EQ(0, unlink(path2) & rmdir(path2)); + ASSERT_EQ(0, unlink(TMP_DIR) & rmdir(TMP_DIR)); +} + +TEST_F(various_address_sockets, scoped_pathname_sockets) +{ + const char *const stream_path = path1; + const char *const dgram_path = path2; + socklen_t size, size_dg; + struct sockaddr_un stream_pathname_addr, dgram_pathname_addr; + int unnamed_sockets[2]; + int stream_pathname_socket, dgram_pathname_socket, + stream_abstract_socket, dgram_abstract_socket; + int pipe_parent[2]; + pid_t child; + int status; + char buf_child; + char data = 'S'; + char buf[5]; + int nbyte; + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM, 0, unnamed_sockets)); + + stream_pathname_addr.sun_family = AF_UNIX; + snprintf(stream_pathname_addr.sun_path, + sizeof(stream_pathname_addr.sun_path), "%s", stream_path); + size = offsetof(struct sockaddr_un, sun_path) + + strlen(stream_pathname_addr.sun_path); + + dgram_pathname_addr.sun_family = AF_UNIX; + snprintf(dgram_pathname_addr.sun_path, + sizeof(dgram_pathname_addr.sun_path), "%s", dgram_path); + size_dg = offsetof(struct sockaddr_un, sun_path) + + strlen(dgram_pathname_addr.sun_path); + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int err, err_dg; + + ASSERT_EQ(0, close(pipe_parent[1])); + + if (variant->domain == SCOPE_SANDBOX) + create_scoped_domain( + _metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + else if (variant->domain == OTHER_SANDBOX) + create_fs_domain(_metadata); + + ASSERT_EQ(0, close(unnamed_sockets[1])); + ASSERT_NE(-1, write(unnamed_sockets[0], &data, sizeof(data))); + ASSERT_EQ(0, close(unnamed_sockets[0])); + + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); + + /* Connect with pathname sockets. */ + stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_LE(0, stream_pathname_socket); + ASSERT_EQ(0, connect(stream_pathname_socket, + &stream_pathname_addr, size)); + dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_LE(0, dgram_pathname_socket); + ASSERT_EQ(0, connect(dgram_pathname_socket, + &dgram_pathname_addr, size_dg)); + + /* Connect with abstract sockets. */ + stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0); + dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + + ASSERT_NE(-1, stream_abstract_socket); + ASSERT_NE(-1, dgram_abstract_socket); + + err = connect(stream_abstract_socket, + &self->stream_address.unix_addr, + self->stream_address.unix_addr_len); + err_dg = connect(dgram_abstract_socket, + &self->dgram_address.unix_addr, + self->dgram_address.unix_addr_len); + if (variant->domain == SCOPE_SANDBOX) { + EXPECT_EQ(-1, err); + EXPECT_EQ(-1, err_dg); + EXPECT_EQ(EPERM, errno); + } else { + EXPECT_EQ(0, err); + EXPECT_EQ(0, err_dg); + } + ASSERT_EQ(0, close(stream_abstract_socket)); + ASSERT_EQ(0, close(dgram_abstract_socket)); + ASSERT_EQ(0, close(stream_pathname_socket)); + ASSERT_EQ(0, close(dgram_pathname_socket)); + _exit(_metadata->exit_code); + return; + } + ASSERT_EQ(0, close(pipe_parent[0])); + + ASSERT_EQ(0, close(unnamed_sockets[0])); + nbyte = read(unnamed_sockets[1], buf, sizeof(buf)); + ASSERT_EQ(sizeof(data), nbyte); + buf[nbyte] = '\0'; + ASSERT_EQ(0, strcmp(&data, buf)); + ASSERT_LE(0, close(unnamed_sockets[1])); + + /* Sets up pathname servers */ + stream_pathname_socket = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_LE(0, stream_pathname_socket); + ASSERT_EQ(0, unlink(stream_path)); + ASSERT_EQ(0, bind(stream_pathname_socket, &stream_pathname_addr, size)); + ASSERT_EQ(0, listen(stream_pathname_socket, backlog)); + + ASSERT_EQ(0, unlink(dgram_path)); + dgram_pathname_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_LE(0, dgram_pathname_socket); + ASSERT_EQ(0, + bind(dgram_pathname_socket, &dgram_pathname_addr, size_dg)); + + /* Set up abstract servers */ + stream_abstract_socket = socket(AF_UNIX, SOCK_STREAM, 0); + dgram_abstract_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, stream_abstract_socket); + ASSERT_NE(-1, dgram_abstract_socket); + ASSERT_EQ(0, + bind(stream_abstract_socket, &self->stream_address.unix_addr, + self->stream_address.unix_addr_len)); + ASSERT_EQ(0, bind(dgram_abstract_socket, &self->dgram_address.unix_addr, + self->dgram_address.unix_addr_len)); + ASSERT_EQ(0, listen(stream_abstract_socket, backlog)); + + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + ASSERT_EQ(child, waitpid(child, &status, 0)); + + ASSERT_EQ(0, close(stream_abstract_socket)); + ASSERT_EQ(0, close(dgram_abstract_socket)); + ASSERT_EQ(0, close(stream_pathname_socket)); + ASSERT_EQ(0, close(dgram_pathname_socket)); + + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + TEST_HARNESS_MAIN From patchwork Thu Sep 5 00:13:59 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tahera Fahimi X-Patchwork-Id: 13791553 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E0C59FC0A; Thu, 5 Sep 2024 00:14:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495269; cv=none; b=p7+7YJxnue3RCcU57OhNEsSMdAd8g9gXI6zsDWM2wLtOCKTHKAMEIgXzSuvCkWifrv3fA8vWB3NLYtpGFvA7MhYHU+c+84r7CDfSnHrsN/mo2MGY9pikoq+Y+J9c9dTogkZTexARJRMJ+4gkOePBFd5p+sd5m3Oyl4APp3jTExQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495269; c=relaxed/simple; bh=6B5/fbYGXDCmpCB56kmnCm2tByrZA7Zjr7RtQx6ILtg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=tDe0QilRo9CVQAPIPpOeJLheW+EYECo7HbScmbFDPW7pysAKQYNOfSSsmljTQT8VjSgdjRopF0NbLI4I6ysGJdIifpFoH+eTMHqz+6lORFH2kK9YAf2sjSYfRvI04oj2FXOPev260SvCzsUeToBJxr54kIwRztZteJt4kOmNPfg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=njilfjm5; arc=none smtp.client-ip=209.85.215.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="njilfjm5" Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-7c3e1081804so158578a12.3; Wed, 04 Sep 2024 17:14:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725495267; x=1726100067; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=UYo96gfr0shigHW7k7ch8rY/tXVue2H6sZrfJvM5RKk=; b=njilfjm5hAFTVPD18G4P1hmD9K7YSNdQgu2FjxRPo5IB1t5S5WVGyCCDgjJKKH2kaS GftnZ4XPuYU4tnNFWTc3zgoOTE4ao5D89P0K3I8j9UoNsDOo1FKo2T8duDoekMQ98p0C XYgyTs/+wDwOk7BLwHqLHBt/HrzRGRsb8z2oOLVA11FrsQctKlVVEUHTfx7EqW4U/Ajp po8/kCCAnkjI5Si2se5wsc95Q0Oi97VL1YwS9ll2Wym4r5qlKfZYLaz90K4goh2ZvTc7 jxF1hc7c6RuV6CitGBTbP8ivnA4UKJKPnhH0IBF6aGMeAytVa9q/GmFjrh+q2L7X7EeF De3Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725495267; x=1726100067; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=UYo96gfr0shigHW7k7ch8rY/tXVue2H6sZrfJvM5RKk=; b=jrasxdyaOjjToSsieP12+s+rHBXpVV9Ie8ZG3jeKg3m5zO9RlhwCXvlyhjYbEvbQEn 0AY9mVTHL9DZ/VC+lgexgWT0HiUhLBDHp7BWA91vHfRMN9WADr+3IIQBmj0Uto995Y0u B4fb+PYAyjcT5VKxB67oenQDnt10hyxg5rV3g1aooQ821HqpCuTG8gzpJfp/w3velUaQ Li7BDNAW1eIfCfljhY9rgrplNceWNCQ/LDz8hdszLZgPp9e38oVoSDpKzmRwEjP2vE9/ 9kTXL/rHZZejMZ8X3nTZmWZL5q7b1AeI/2MeDAF4OH/PYxJCe8Vut027xYjT574qk7h7 qS4g== X-Forwarded-Encrypted: i=1; AJvYcCVTxm00Ez2kaE7qCV0qeh3YSDqLa+jypvH3vlchdFAp7dLba57F5bdv30DZzKyqxc7FuQ8jKRIWDZnGSKflk7yRC63lRhTk@vger.kernel.org, AJvYcCVaJlWpcBGh9oTiLyqwzKFCA8cWh/t2S81Rp0gHiCRGVuyEphJwiZRRAjz0na9LzRxyJiV1PGD8@vger.kernel.org, AJvYcCXR5ma/GHcp9Gtc+2m6kBQ9HrF6pBycBWtek/6+ptQp7p8i6jgA3ICW0e/EfVVK4511XREJQcyQGzsIzbc=@vger.kernel.org X-Gm-Message-State: AOJu0YyrXrFMaGSvP3IoFa8bGf491srykQsQnlBOralTkJlOYhzOphNg napbu1jA1FR19SjSzGnGnmzqLYIwYnc3z74imLi2082lxHGLZdx3VelWha0H X-Google-Smtp-Source: AGHT+IGDPU/BNHAZz0bwWU8S2tHlNB+zCpsSMRBxuqOoSw8vPfK18pVrIafwCMVe5MFMa3TZMSUUkg== X-Received: by 2002:a05:6a20:43a8:b0:1be:c3c1:7be8 with SMTP id adf61e73a8af0-1cce1015c4dmr23641835637.26.1725495266908; Wed, 04 Sep 2024 17:14:26 -0700 (PDT) Received: from tahera-OptiPlex-5000.tail3bf47f.ts.net ([136.159.49.123]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71778534921sm2159781b3a.76.2024.09.04.17.14.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Sep 2024 17:14:26 -0700 (PDT) From: Tahera Fahimi To: outreachy@lists.linux.dev Cc: mic@digikod.net, gnoack@google.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, bjorn3_gh@protonmail.com, jannh@google.com, netdev@vger.kernel.org, Tahera Fahimi Subject: [PATCH v11 5/8] selftests/landlock: Test connected vs non-connected datagram UNIX socket Date: Wed, 4 Sep 2024 18:13:59 -0600 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This patch checks the specific case where a scoped datagram socket is connected and send(2) works, whereas sendto(2) is denied if the datagram socket is not connected. Signed-off-by: Tahera Fahimi --- .../landlock/scoped_abstract_unix_test.c | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c index 8fc47e45d17e..39297ebf7b73 100644 --- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c @@ -819,4 +819,109 @@ TEST_F(various_address_sockets, scoped_pathname_sockets) _metadata->exit_code = KSFT_FAIL; } +TEST(datagram_sockets) +{ + struct service_fixture connected_addr, non_connected_addr; + int conn_sock, non_conn_sock; + int pipe_parent[2], pipe_child[2]; + int status; + char buf; + pid_t child; + int num_bytes; + char data[64]; + + drop_caps(_metadata); + memset(&connected_addr, 0, sizeof(connected_addr)); + set_unix_address(&connected_addr, 0); + memset(&non_connected_addr, 0, sizeof(non_connected_addr)); + set_unix_address(&non_connected_addr, 1); + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + char buf_data[64]; + + ASSERT_EQ(0, close(pipe_parent[1])); + ASSERT_EQ(0, close(pipe_child[0])); + + conn_sock = socket(AF_UNIX, SOCK_DGRAM, 0); + non_conn_sock = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, conn_sock); + ASSERT_NE(-1, non_conn_sock); + + ASSERT_EQ(1, read(pipe_parent[0], &buf, 1)); + + ASSERT_EQ(0, connect(conn_sock, &connected_addr.unix_addr, + connected_addr.unix_addr_len)); + + /* Both connected and non-connected sockets can send + * data when the domain is not scoped. + */ + memset(buf_data, 'x', sizeof(buf_data)); + ASSERT_NE(-1, send(conn_sock, buf_data, sizeof(buf_data), 0)); + ASSERT_NE(-1, sendto(non_conn_sock, buf_data, sizeof(buf_data), + 0, &non_connected_addr.unix_addr, + non_connected_addr.unix_addr_len)); + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); + + /* Scopes the domain. */ + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + /* + * Connected socket sends data to the receiver, but the + * non-connected socket must fail to send data. + */ + ASSERT_NE(-1, send(conn_sock, buf_data, sizeof(buf_data), 0)); + ASSERT_EQ(-1, sendto(non_conn_sock, buf_data, sizeof(buf_data), + 0, &non_connected_addr.unix_addr, + non_connected_addr.unix_addr_len)); + ASSERT_EQ(EPERM, errno); + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); + + EXPECT_EQ(0, close(conn_sock)); + EXPECT_EQ(0, close(non_conn_sock)); + _exit(_metadata->exit_code); + return; + } + ASSERT_EQ(0, close(pipe_parent[0])); + ASSERT_EQ(0, close(pipe_child[1])); + + conn_sock = socket(AF_UNIX, SOCK_DGRAM, 0); + non_conn_sock = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, conn_sock); + ASSERT_NE(-1, non_conn_sock); + + ASSERT_EQ(0, bind(conn_sock, &connected_addr.unix_addr, + connected_addr.unix_addr_len)); + ASSERT_EQ(0, bind(non_conn_sock, &non_connected_addr.unix_addr, + non_connected_addr.unix_addr_len)); + + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + + ASSERT_EQ(1, read(pipe_child[0], &buf, 1)); + num_bytes = recv(conn_sock, data, sizeof(data) - 1, 0); + ASSERT_NE(-1, num_bytes); + num_bytes = recv(non_conn_sock, data, sizeof(data) - 1, 0); + ASSERT_NE(-1, num_bytes); + + /* + * Connected datagram socket will receive data, but + * non-connected datagram socket does not receive data. + */ + ASSERT_EQ(1, read(pipe_child[0], &buf, 1)); + num_bytes = recv(conn_sock, data, sizeof(data) - 1, 0); + ASSERT_NE(-1, num_bytes); + + EXPECT_EQ(0, close(conn_sock)); + EXPECT_EQ(0, close(non_conn_sock)); + ASSERT_EQ(child, waitpid(child, &status, 0)); + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + TEST_HARNESS_MAIN From patchwork Thu Sep 5 00:14:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tahera Fahimi X-Patchwork-Id: 13791554 Received: from mail-oo1-f50.google.com (mail-oo1-f50.google.com [209.85.161.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6C6A0168DC; Thu, 5 Sep 2024 00:14:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495271; cv=none; b=nh3Oi6u+jK8/60a6PffmjauPT5bvOUbCHYGaKmAs28hpetSVE6iq+zfgehESlGL1EStNM7+o0ZeFC4WLeeK2BFU5TdVWQgaaa7g/ZhkCqHsANuS+nZxJctcr2/lv1DVEtlfljpUUr0cCiAuAZSxhUlih5yRkpahMreBBQPnbazA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495271; c=relaxed/simple; bh=RqWkchAHwryayt3Hw0HpIWRhsf16JdaX/v77cAvyw3g=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=W4C0xFimmqY2SQrezih2X81VvG+G+buYhjF209BD4k6P6FQbmWxSgFcY8vs9CFdvXx9lmPmQtg8f+DRw6nPgzMYl8ZOKk/vyrxcEym9QWVi8oIdyy5Sy3mqXqjKNiB9k8TgfE6Zilf6Bhjm7VQW+9rle702OZFHmQ2iWO12oI7s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=UVjW1i0H; arc=none smtp.client-ip=209.85.161.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="UVjW1i0H" Received: by mail-oo1-f50.google.com with SMTP id 006d021491bc7-5de8b17db8dso128890eaf.2; Wed, 04 Sep 2024 17:14:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725495268; x=1726100068; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=VLU/Dks+aXWcA0j3NcWT/JOFKYGYOLpEI0gipU7ELuY=; b=UVjW1i0HMP6ikDyExjeZNmqGS9PEsAR1GDFJxZnaIOpo+8mIWQOeoSj52PXhyYDix7 2ynMSUKwP3beB2gR3WUl3FocMZCxPL/LUUoeITlrouqb49AVfMmJZr3XgZi5MT6MPfHv zQ2Zge1CN60vk4QcSE3iQmwElIBBQ5FMJnhqZrjw5K9uyUb0yPwywwJPjTLX4wxxNa6C iJJZWj2BYDrRmoGEaf+Z092LyHNS3Cf9rND4s37exo4OWGFE6FFkp4rLl1NIw78vpXEW /CA2LIjT3+NVNzmzuUErSQPU8wuIBHxS9+tNIxsKdFWmOTXCr6N0FaCbIbLUPbLHH7qG fVuQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725495268; x=1726100068; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=VLU/Dks+aXWcA0j3NcWT/JOFKYGYOLpEI0gipU7ELuY=; b=IiHCC5RZK1OizpWYIbLiSFofA/eystLI9hi17JvsH313B2h4SaYpnS/5CyCVUkOcix UfnAQIzar5hYk+alFP7zt5a/XFOQGhpHGG6bfQnErVD9OZ1+eBhXariaJs5tQxm9MRSg pYB8oaR0b741kRxZQ36UMR1St4Kjl9qQJGa059xlwRQHTVb7rjmC6u91F5lbw4WssX+9 kiJRZlANZcTYvEyW8Crh43jU9Yjl7BOVN3TmM9z7udM/G4CEe+vYQYxZPi+cjOv2w+bB OC/F7HDq7XhgvAQ6MZuWVNDTe3OjWocjLCPoibEzghhrmZJXh59iK7EkDbsLP58DJQX9 JCwg== X-Forwarded-Encrypted: i=1; AJvYcCUV3u91dTCF1ogWKYFQCemRkww31YzIRCdDpf9NHOpfoXuT0LmITXIjEv1CqRp88e5AwVl1K9s/BdrdQKE=@vger.kernel.org, AJvYcCVjxDcWmgYOFJ0wxMosMqZXxx1xKQl2JHVEP12TMMOO+wWMM6qPilb+eDedfeqPkRC+6GsYSxhw@vger.kernel.org, AJvYcCWm6taoIQEOLzDUU7hZJ/NidfeWJLiJdbAs07ZlMiFJHWkHoSZ6heGVZ+2tuJnmPJfy4PSombwuUdXki0q+cYYz8ik/8zkg@vger.kernel.org X-Gm-Message-State: AOJu0YwFbLadktyWWzUGYT4IscRe00cV9sV1wDzo1J7co+P30BmGSohR BFyn5LjQI5PdLeb2PjzBDq1gNIuYXb0l1IHKhBHEMv0cMooTLc36 X-Google-Smtp-Source: AGHT+IEYBzaZziqgePl/VAJRK2y9nblLr4dKcr/reyFKQBYG8x3W9aZIf9LB6cNhGm5u/xeQg76/dg== X-Received: by 2002:a05:6358:e48b:b0:1b5:f592:6729 with SMTP id e5c5f4694b2df-1b603c200f6mr410937955d.9.1725495268523; Wed, 04 Sep 2024 17:14:28 -0700 (PDT) Received: from tahera-OptiPlex-5000.tail3bf47f.ts.net ([136.159.49.123]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71778534921sm2159781b3a.76.2024.09.04.17.14.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Sep 2024 17:14:27 -0700 (PDT) From: Tahera Fahimi To: outreachy@lists.linux.dev Cc: mic@digikod.net, gnoack@google.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, bjorn3_gh@protonmail.com, jannh@google.com, netdev@vger.kernel.org, Tahera Fahimi Subject: [PATCH v11 6/8] selftests/landlock: Restrict inherited datagram UNIX socket to connect Date: Wed, 4 Sep 2024 18:14:00 -0600 Message-Id: <1428574deec13603b6ab2f2ed68ecbfa3b63bcb3.1725494372.git.fahimitahera@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 A socket can be shared between multiple processes, so it can connect and send data to them. This patch provides a test scenario where a sandboxed process inherits a socket's file descriptor. The process cannot connect or send data to the inherited socket since the process is scoped. Signed-off-by: Tahera Fahimi --- .../landlock/scoped_abstract_unix_test.c | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c index 39297ebf7b73..97ef74ce9f49 100644 --- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c +++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c @@ -924,4 +924,70 @@ TEST(datagram_sockets) _metadata->exit_code = KSFT_FAIL; } +TEST(self_connect) +{ + struct service_fixture connected_addr, non_connected_addr; + int connected_socket, non_connected_socket, status; + pid_t child; + + drop_caps(_metadata); + memset(&connected_addr, 0, sizeof(connected_addr)); + set_unix_address(&connected_addr, 0); + memset(&non_connected_addr, 0, sizeof(non_connected_addr)); + set_unix_address(&non_connected_addr, 1); + + connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + non_connected_socket = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT_NE(-1, connected_socket); + ASSERT_NE(-1, non_connected_socket); + + ASSERT_EQ(0, bind(connected_socket, &connected_addr.unix_addr, + connected_addr.unix_addr_len)); + ASSERT_EQ(0, bind(non_connected_socket, &non_connected_addr.unix_addr, + non_connected_addr.unix_addr_len)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + char buf_data[64]; + + memset(buf_data, 'x', sizeof(buf_data)); + /* Child's domain is scoped. */ + create_scoped_domain(_metadata, + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET); + + /* + * The child inherits the sockets, and cannot connect or + * send data to them. + */ + ASSERT_NE(0, + connect(connected_socket, &connected_addr.unix_addr, + connected_addr.unix_addr_len)); + ASSERT_EQ(EPERM, errno); + + ASSERT_EQ(-1, + sendto(connected_socket, buf_data, sizeof(buf_data), + 0, &connected_addr.unix_addr, + connected_addr.unix_addr_len)); + ASSERT_EQ(EPERM, errno); + + ASSERT_EQ(-1, sendto(non_connected_socket, buf_data, + sizeof(buf_data), 0, + &non_connected_addr.unix_addr, + non_connected_addr.unix_addr_len)); + ASSERT_EQ(EPERM, errno); + + EXPECT_EQ(0, close(connected_socket)); + EXPECT_EQ(0, close(non_connected_socket)); + _exit(_metadata->exit_code); + return; + } + EXPECT_EQ(0, close(connected_socket)); + EXPECT_EQ(0, close(non_connected_socket)); + ASSERT_EQ(child, waitpid(child, &status, 0)); + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + TEST_HARNESS_MAIN From patchwork Thu Sep 5 00:14:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tahera Fahimi X-Patchwork-Id: 13791555 Received: from mail-oo1-f45.google.com (mail-oo1-f45.google.com [209.85.161.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CFC0528EB; Thu, 5 Sep 2024 00:14:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495272; cv=none; b=iYguu8EixlBXmg/LwV50ENed3HGpsNf9gt4s/JLCiTAyUO5Y/NXglF844ZIcMjfawfu1h/lru2Dric7cznbOvhdmfLzC6m9kV4dVu3C4QRVRoGjLfH6tnjX1CAn7H7tFZsy4L8xtxHn7Tg3v1CZikM0+an1SmzHT8VuTGP2+DOk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495272; c=relaxed/simple; bh=3eeRu4zbZB1MuGmyZVhbliCojVX5Q+y6lx7CXpm0fh4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=R2j4Xyvvx9f8o2a7vINwFoixHa/cNCa5fgNtDn7vzmj2po24yTtqyL1r2Hq8BXqangQ9UJv8Z2w4b8YomMuMHuJhgB5k/SXPc6uioXUo1+JSfmf+UKjW73OpPZ21R73LR3WM66q5/mDIVT6DhndJJjrxGHW2Sr8+sFu8+i+5Pm4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=I0paisN7; arc=none smtp.client-ip=209.85.161.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="I0paisN7" Received: by mail-oo1-f45.google.com with SMTP id 006d021491bc7-5dd43ab0458so147725eaf.0; Wed, 04 Sep 2024 17:14:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725495270; x=1726100070; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Nb5jw/WoOQPFLaFxTOIP9Tl9HcnrTWdqDgrxkbTFv9o=; b=I0paisN7f07ANEr/v+HTox9gAVSsu4gNMyOjBEYnVybpbZp9e8SCb6uA2rhbppxZ7R nqxsBpPmKWDySW3kexQFbsv3TKBNpP2Bmx6cFgxof45JuXCHeL89P5yTHvsVYGKOaja/ LIaefhmtzOljEvU6iyHpnKXs2RbbIWIlD0DIyccJW1sDzGQ0L4Sm72MMm3VqLD9uWVuL e2JYJmnK8XBkx3rzzrbii1BI7yoda7UgqnVXxVE8j84pt5M8JelzfgxTuXrtg78bf+Yi PGJ9PNHE2EgpC8zn5ir3ETHw3Bmb+0f55X3cBqGoCz+4sa2i2QhncTxaHu4Yoa7IxQ9/ 8Hpw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725495270; x=1726100070; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Nb5jw/WoOQPFLaFxTOIP9Tl9HcnrTWdqDgrxkbTFv9o=; b=WBW+ei0n7w5CGHfyTMGcyJ1VUE67GZR925viGG0AJORXAS61cIE6YIwjP1spg8JyVn SxeZOIpUFuDVMK6WfkCbAoPygCIkWAzzRJpSKDeCydJLd1sjFZlNxryRvo+OoFHcn/71 xntXEx76QUczCIXxNzQ0IY4JesYreDP0q+iQpYzUkcCliX1tHzMkPTd3LKD7LF3QnA6Y rGbsnbFo9+FcKSZY/3/6Xr0tDNVWvjr4Z/lFPzO07J1Jqv9/W/fdkkb9JMJsuQJLylah UUeSlyzAku4L9urYd7+wkFX8EdHyFww6A2MxKh1rMzvQcjtNuGoBoP8JHEMZFBFfS8N5 NdnA== X-Forwarded-Encrypted: i=1; AJvYcCVA1B8fibhuOa9MGCMc79B8P4vXkDYgOqIyDCe83v/Y1KIn2XzL3N5pC4tT8EpFiLm8dv8suPjHmqSj1mSBp2DNNum+Yril@vger.kernel.org, AJvYcCW1pqCc655k5hVo5QcPJEJ/BDCY9zUCveHLh93wTLltmx6BSWAOV59mQgyJerQjtESfuNPvpBAIkd1EVag=@vger.kernel.org, AJvYcCW2nYHhm7spGI0e7EFT4Ic77/8/6JvztFg7sPBqdw8bpRK3oakI65EmcQcluhWUFjMyIKShSOl+@vger.kernel.org X-Gm-Message-State: AOJu0YzllZa8V/j+BDk/CS4DEwRlGBhnkbjQAlWjTYyh4tDyyfDlr5xJ kPnX3cpTrb94Ou3OLvwsfkqb7hDMdy0p6YCcDneKpcpG5/dq5FGlLUI94EA+ X-Google-Smtp-Source: AGHT+IF/swT4/QCQUaShc+LWLrYgsQ803/PfQesWXvFDsSqr4DHMuf7CQ8p1bPaggbUhXEZiXo2vvw== X-Received: by 2002:a05:6870:ab13:b0:260:e678:b653 with SMTP id 586e51a60fabf-277d06c890emr17533784fac.42.1725495269915; Wed, 04 Sep 2024 17:14:29 -0700 (PDT) Received: from tahera-OptiPlex-5000.tail3bf47f.ts.net ([136.159.49.123]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71778534921sm2159781b3a.76.2024.09.04.17.14.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Sep 2024 17:14:29 -0700 (PDT) From: Tahera Fahimi To: outreachy@lists.linux.dev Cc: mic@digikod.net, gnoack@google.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, bjorn3_gh@protonmail.com, jannh@google.com, netdev@vger.kernel.org, Tahera Fahimi Subject: [PATCH v11 7/8] sample/landlock: Add support abstract UNIX socket restriction Date: Wed, 4 Sep 2024 18:14:01 -0600 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 A sandboxer can receive the character "a" as input from the environment variable LL_SCOPE to restrict the abstract UNIX sockets from connecting to a process outside its scoped domain. Example ======= Create an abstract UNIX socket to listen with socat(1): socat abstract-listen:mysocket - Create a sandboxed shell and pass the character "a" to LL_SCOPED: LL_FS_RO=/ LL_FS_RW=. LL_SCOPED="a" ./sandboxer /bin/bash Note that any other form of input(e.g. "a:a", "aa", etc) is not acceptable. If the sandboxed process tries to connect to the listening socket with command "socat - abstract-connect:mysocket", the connection will fail. Signed-off-by: Tahera Fahimi --- v11: - Change implementation of check_ruleset_scope function to make it less bug prone. - Imptovement on the commit description. v10: - Minor improvement in code based on v9. v9: - Add a restrict approach on input of LL_SCOPED, so it only allows zero or one "a" to be the input. v8: - Adding check_ruleset_scope function to parse the scope environment variable and update the landlock attribute based on the restriction provided by the user. - Adding Mickaël Salaün reviews on version 7. v7: - Adding IPC scoping to the sandbox demo by defining a new "LL_SCOPED" environment variable. "LL_SCOPED" gets value "a" to restrict abstract unix sockets. - Change LANDLOCK_ABI_LAST to 6. --- samples/landlock/sandboxer.c | 61 +++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index e8223c3e781a..18d072c23a23 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #ifndef landlock_create_ruleset static inline int @@ -55,6 +57,7 @@ static inline int landlock_restrict_self(const int ruleset_fd, #define ENV_FS_RW_NAME "LL_FS_RW" #define ENV_TCP_BIND_NAME "LL_TCP_BIND" #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" +#define ENV_SCOPED_NAME "LL_SCOPED" #define ENV_DELIMITER ":" static int parse_path(char *env_path, const char ***const path_list) @@ -184,6 +187,45 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd, return ret; } +static bool check_ruleset_scope(const char *const env_var, + struct landlock_ruleset_attr *ruleset_attr) +{ + bool abstract_scoping = false; + bool ret = true; + char *env_type_scope, *env_type_scope_next, *ipc_scoping_name; + + /* scoping is not supported by Landlock ABI */ + if (!(ruleset_attr->scoped & LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET)) + return ret; + + env_type_scope = getenv(env_var); + /* scoping is not supported by the user */ + if (!env_type_scope || strcmp("", env_type_scope) == 0) { + ruleset_attr->scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET; + return ret; + } + + env_type_scope = strdup(env_type_scope); + unsetenv(env_var); + env_type_scope_next = env_type_scope; + while ((ipc_scoping_name = + strsep(&env_type_scope_next, ENV_DELIMITER))) { + if (strcmp("a", ipc_scoping_name) == 0 && !abstract_scoping) { + abstract_scoping = true; + } else { + fprintf(stderr, "Unsupported scoping \"%s\"\n", + ipc_scoping_name); + ret = false; + goto out_free_name; + } + } + if (!abstract_scoping) + ruleset_attr->scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET; +out_free_name: + free(env_type_scope); + return ret; +} + /* clang-format off */ #define ACCESS_FS_ROUGHLY_READ ( \ @@ -208,7 +250,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd, /* clang-format on */ -#define LANDLOCK_ABI_LAST 5 +#define LANDLOCK_ABI_LAST 6 int main(const int argc, char *const argv[], char *const *const envp) { @@ -223,14 +265,15 @@ int main(const int argc, char *const argv[], char *const *const envp) .handled_access_fs = access_fs_rw, .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, + .scoped = LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET, }; if (argc < 2) { fprintf(stderr, - "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s " + "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s " " [args]...\n\n", ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, - ENV_TCP_CONNECT_NAME, argv[0]); + ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]); fprintf(stderr, "Execute a command in a restricted environment.\n\n"); fprintf(stderr, @@ -251,15 +294,18 @@ int main(const int argc, char *const argv[], char *const *const envp) fprintf(stderr, "* %s: list of ports allowed to connect (client).\n", ENV_TCP_CONNECT_NAME); + fprintf(stderr, "* %s: list of restrictions on IPCs.\n", + ENV_SCOPED_NAME); fprintf(stderr, "\nexample:\n" "%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" " "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " "%s=\"9418\" " "%s=\"80:443\" " + "%s=\"a\" " "%s bash -i\n\n", ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, - ENV_TCP_CONNECT_NAME, argv[0]); + ENV_TCP_CONNECT_NAME, ENV_SCOPED_NAME, argv[0]); fprintf(stderr, "This sandboxer can use Landlock features " "up to ABI version %d.\n", @@ -327,6 +373,10 @@ int main(const int argc, char *const argv[], char *const *const envp) /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV; + __attribute__((fallthrough)); + case 5: + /* Removes LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */ + ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET; fprintf(stderr, "Hint: You should update the running kernel " "to leverage Landlock features " @@ -358,6 +408,9 @@ int main(const int argc, char *const argv[], char *const *const envp) ~LANDLOCK_ACCESS_NET_CONNECT_TCP; } + if (!check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr)) + return 1; + ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); if (ruleset_fd < 0) { From patchwork Thu Sep 5 00:14:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tahera Fahimi X-Patchwork-Id: 13791556 Received: from mail-oa1-f47.google.com (mail-oa1-f47.google.com [209.85.160.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C4A571DDEA; Thu, 5 Sep 2024 00:14:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495273; cv=none; b=LN1yWOdC+lrf0Ga8CvUl9P/eAblZA+a93wTlIuTwF9z8zEISJvcBWsL9aU+00fuCwDO5LDZwwlNVR7+cHdCu+JpVJX8KS/PkpEAPczGhy215Pukh2HG9nCE8WK1BQOAO33UR2ovrCe5c57M8oSELSsg5CZV+/XALI5hcSlMKF6k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1725495273; c=relaxed/simple; bh=n6AgMjmb2jzjsD5uIIArajKwcjywOgVITJCK0KBH+y4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=eJqdNRABhpGoFuVX1mDL99uqnb77op2kLyGLgCcx6jCXRMNy2SsP/qCf8Y2nyYEvoUap/Nbfw+QL1QhrZ29+5dZNPlSaJCDpv8O2v/LBOda3k3lbElPtpfwTRXBKlPL5cGzHlh8H8iT1u8p3PvCyDny9pJkipBtS8PqpPkG8458= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ckVQj8m5; arc=none smtp.client-ip=209.85.160.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ckVQj8m5" Received: by mail-oa1-f47.google.com with SMTP id 586e51a60fabf-26ff21d82e4so142867fac.2; Wed, 04 Sep 2024 17:14:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1725495271; x=1726100071; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=uGMSf+BOMvZ54AzTEvMeQPkYD6qUwye83VZEdQAhs/Y=; b=ckVQj8m5k9EE2lMb+wqCvjnc2Vs4q0V2uolNY2REMoKQ8XuqrWsm7HYKfUEB9OhlyM IJebXC4JtqVdF/BgOuWiz1eQPNj52E3v1HiDD20FFsqvI8qqfsTd0NJFMfAKeF8n3yAv R2hDQ21/SimBW38AjLWND/LZJFZiTDSNgRxXJjxqnRmnT1gfo4WjQRNUp69K+lAB3qE7 GAcsvTCudqtmxkCEaZrm8LFV7JNS/83WVaDI92lBb4Q9CYDdnTifU51a3aTWLklEpUiq m9LsTnUfYVQmP36ezC5QWXKV8hc3mHatCNhHxvg7b8YgluE3uTNvGMgvoqFGceSx1KTv xT6w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725495271; x=1726100071; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=uGMSf+BOMvZ54AzTEvMeQPkYD6qUwye83VZEdQAhs/Y=; b=jwQBIj2iPR/+jvOLFMuKjCb5NBIhl1mDjfGEu/56+PIJ/0/j8bpS/2MVDMMx2/IMr2 MFCjGfLzxCIeDsoYZ/DidHMxYAiDiSEiiMR5D0ekp01fiWn+//X/b49/YtQZc/otdZLu H+e2MEF+Cp3ozOZv9piM1SkhknVQpOkUCSjjR2lBfDJz1AOc6k709hURQoc4nNoRoHUx C5TguMr0PUFmBEXXWER4carKlHiQZrasEMK0e+9RWfxu2JcGSohmL7yVQVT67ObPMNSX ct0aGONXFYB592JUWH+o9Mz0zPCOW+VSdtUJ7JOV9kJ1pD5lIQw6OkvmxZ5GoWu53zEG O/xw== X-Forwarded-Encrypted: i=1; AJvYcCU1s02Eo+2XXxeuBXNkhY3z5x6k6A4cdEXRT3nup+dy/hGKB8zRjBwqblZ7PBHZnTFC1K7ZUznVSoSpRPM=@vger.kernel.org, AJvYcCVX8NFtiArFVA1NkLxKj+nVX89jZ+TNa4+31uaFNy2rRw6XHS6npOadHgUMsNbM5LeSx93nUn+8@vger.kernel.org, AJvYcCWf6O5S2m8PWzqQruQI8A8+I7PXFIvMVm519PGaIsZwmKWbDd2V9YWP5FUj8n9E6XxVwsvwmBTohPMwfiGeXa6iRrTEGiZd@vger.kernel.org X-Gm-Message-State: AOJu0YzeniAFKu+OkrJDYeOwTeBHhGkT/+WjnnKH7RNT4B2UKHW9mRMk 4nB0/ovse7vWqRxt5YgJRFsGXzlZufPPGcORKDrnGOYTFclx58L8 X-Google-Smtp-Source: AGHT+IEFkBdiQ9mIrNMKcS7w3UIAuGCobjurYy6KLJ90BaABI+/F2r4VxGZAHpnI7d8X+tlZa9qy1g== X-Received: by 2002:a05:6870:64a9:b0:277:e512:f27a with SMTP id 586e51a60fabf-277e512f84emr13912948fac.16.1725495270894; Wed, 04 Sep 2024 17:14:30 -0700 (PDT) Received: from tahera-OptiPlex-5000.tail3bf47f.ts.net ([136.159.49.123]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71778534921sm2159781b3a.76.2024.09.04.17.14.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 04 Sep 2024 17:14:30 -0700 (PDT) From: Tahera Fahimi To: outreachy@lists.linux.dev Cc: mic@digikod.net, gnoack@google.com, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, bjorn3_gh@protonmail.com, jannh@google.com, netdev@vger.kernel.org, Tahera Fahimi Subject: [PATCH v11 8/8] Landlock: Document LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET and ABI version Date: Wed, 4 Sep 2024 18:14:02 -0600 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-security-module@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Introducing LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET as an IPC scoping mechanism in Landlock ABI version 6, and updating ruleset_attr, Landlock ABI version, and access rights code blocks based on that. Signed-off-by: Tahera Fahimi --- v11: - Documentation cases where i) a connected datagram UNIX socket send(2)/ sendto(2) data, but it is denied when the socket is not connected, and ii) a scoped process cannot connect by an inherited socket's file descriptor. v10: - Update date. v8: - Improving documentation by specifying differences between scoped and non-scoped domains. - Adding review notes of version 7. - Update date. v7: - Add "LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET" explanation to IPC scoping section and updating ABI to version 6. - Adding "scoped" attribute to the Access rights section. - In current limitation, unnamed sockets are specified as sockets that are not restricted. - Update date. --- Documentation/userspace-api/landlock.rst | 45 ++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst index 37dafce8038b..c3b87755e98d 100644 --- a/Documentation/userspace-api/landlock.rst +++ b/Documentation/userspace-api/landlock.rst @@ -8,7 +8,7 @@ Landlock: unprivileged access control ===================================== :Author: Mickaël Salaün -:Date: July 2024 +:Date: August 2024 The goal of Landlock is to enable to restrict ambient rights (e.g. global filesystem or network access) for a set of processes. Because Landlock @@ -81,6 +81,8 @@ to be explicit about the denied-by-default access rights. .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, + .scoped = + LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET, }; Because we may not know on which kernel version an application will be @@ -119,6 +121,9 @@ version, and only use the available subset of access rights: case 4: /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV; + case 5: + /* Removes LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET for ABI < 6 */ + ruleset_attr.scoped &= ~LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET; } This enables to create an inclusive ruleset that will contain our rules. @@ -306,6 +311,35 @@ To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target process, a sandboxed process should have a subset of the target process rules, which means the tracee must be in a sub-domain of the tracer. +IPC Scoping +----------- + +Similar to the implicit `Ptrace restrictions`_, we may want to further +restrict interactions between sandboxes. Each Landlock domain can be +explicitly scoped for a set of actions by specifying it on a ruleset. +For example, if a sandboxed process should not be able to +:manpage:`connect(2)` to a non-sandboxed process through abstract +:manpage:`unix(7)` sockets, we can specify such restriction with +``LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET``. + +A sandboxed process can connect to a non-sandboxed process when its +domain is not scoped. If a process's domain is scoped, it can only +connect to sockets created by processes in the same scoped domain. + +A connected datagram socket behaves like a stream socket when its domain +is scoped, meaning if the domain is scoped after the socket is connected +, it can still :manpage:`send(2)` data just like a stream socket. +However, in the same scenario, a non-connected datagram socket cannot +send data (with :manpage:`sendto(2)`) outside its scoped domain. + +A process with a scoped domain can inherit a socket created by a +non-scoped process. The process cannot connect to this socket since it +has a scoped domain. + +IPC scoping does not support Landlock rules, so if a domain is scoped, +no rules can be added to allow access to a resource outside of the +scoped domain. + Truncating files ---------------- @@ -404,7 +438,7 @@ Access rights ------------- .. kernel-doc:: include/uapi/linux/landlock.h - :identifiers: fs_access net_access + :identifiers: fs_access net_access scope Creating a new ruleset ---------------------- @@ -541,6 +575,13 @@ earlier ABI. Starting with the Landlock ABI version 5, it is possible to restrict the use of :manpage:`ioctl(2)` using the new ``LANDLOCK_ACCESS_FS_IOCTL_DEV`` right. +Abstract UNIX sockets Restriction (ABI < 6) +-------------------------------------------- + +With ABI version 6, it is possible to restrict connection to an abstract +Unix socket through ``LANDLOCK_SCOPED_ABSTRACT_UNIX_SOCKET``, thanks to +the ``scoped`` ruleset attribute. + .. _kernel_support: Kernel support