From patchwork Tue Mar 4 01:12:57 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999742 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0A3A123F383; Tue, 4 Mar 2025 01:16:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741050985; cv=none; b=fUiwj2VRuS9B3i7djyxFwp5BSj7tc3J4i4gBrunmUjycjiBkDyojwyGq8rIREaLukqxNghAexrzwJ72qpE4DqeTJmyQgWxKwMi5RX4aml92ysoIzcc5w1MsZqZIu4VQThySaXUWwLoRiY/1BsQJLKCEB/58MBLuidibZ9PsrGxw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741050985; c=relaxed/simple; bh=GB5PTejEb52DlcHgyWLNIgsn0TZHai1ZJRw5pAJ76h0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=hRayWKJ+v4heHXZxpjqU9F6wafiOBVSJn8X34MQ45M9k6gsxvlCIdMKvYDVYNTq/a94vrl1v94GOCb0m/mCBRf9pX3iGyIwuB3g2IpJacR0/a69UmwljE2sIhwXWZPMV0QWXn157gakZ+RvnxrXocqsJleevttu1yEgmrMZqnmw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=m0t94J/h; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=Bx5cACyV; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="m0t94J/h"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="Bx5cACyV" Received: from phl-compute-05.internal (phl-compute-05.phl.internal [10.202.2.45]) by mailflow.stl.internal (Postfix) with ESMTP id D33371D413F5; Mon, 3 Mar 2025 20:16:22 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-05.internal (MEProxy); Mon, 03 Mar 2025 20:16:23 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to; s=fm1; t=1741050982; x=1741054582; bh=Gl2rUkxfUrKKVIO5dmMkfvs2cEint31zV1hl5A3zvNE=; b= m0t94J/hB7ZxJkwe5Buckj39dUA5of+/mcVaB4KzR09G2xjZGn5Er+heClD83Yhz W06xNYq2QMj88tQzGCu0MWHc7ltYy/W2v80F2xVl84+aGTuHVYhKzE200iUGexvk srZ2f19mqsQdOWQbr+QZdBWACp8vlacXqbiu4NcFpJyUZDT4TiO5vTJt4qdI6ak1 dMvyHPn/5F7EtuffUUtUXMsevbA8BOHnDMTVqB7+K3csyGQ9mHTCudOOd6WMATps 1iFK1m88G0S6jVCITIwiJ1p4Ni+FUNi2yawFHcD5RSfHbHIQpEYKDpQ73El1Z28J hnBsjp7Dg3T09tSCxN/hGQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm1; t=1741050982; x= 1741054582; bh=Gl2rUkxfUrKKVIO5dmMkfvs2cEint31zV1hl5A3zvNE=; b=B x5cACyVI/9Xm+y2bWssNsNdtXuR46GXBFTwRnKrDGMRS9Byon7HpeR7PlsVYTUSa kO5vruvUDx7W960wE4BEvCUgCO0bCWTqbT04yQxxRKNvVsLaE90IlZz9YpiYeYJz edPJFvb373TXcaG6yzTTxa9C/CuMo/yWz0yhTrt4cWPxA4EKmCgqcn5IAmR4hUii byJZnTMnFxbHUgMQo3do1BUE4zcuG5HJXdrrD0ifq2qgENkMlrDnMYWma5m9kj2Q q2jIFjYvxeVn7goktJWWHsL9P9yPH8gyk3X+XvYc0l83WZMlf0WImfywMWEVtQea dqCqmAI2SDHp1/3nh99jA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieeiucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhggtgfgsehtkeertder tdejnecuhfhrohhmpefvihhnghhmrghoucghrghnghcuoehmsehmrghofihtmhdrohhrgh eqnecuggftrfgrthhtvghrnhepieeigeeghedtffeifffhkeeuffehhfevuefgvdekjeek hedvtedtgfdvgefhudejnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrg hilhhfrhhomhepmhesmhgrohifthhmrdhorhhgpdhnsggprhgtphhtthhopeelpdhmohgu vgepshhmthhpohhuthdprhgtphhtthhopehmihgtseguihhgihhkohgurdhnvghtpdhrtg hpthhtohepghhnohgrtghksehgohhoghhlvgdrtghomhdprhgtphhtthhopehjrggtkhes shhushgvrdgtiidprhgtphhtthhopehmsehmrghofihtmhdrohhrghdprhgtphhtthhope hlihhnuhigqdhsvggtuhhrihhthidqmhhoughulhgvsehvghgvrhdrkhgvrhhnvghlrdho rhhgpdhrtghpthhtoheprghmihhrjeefihhlsehgmhgrihhlrdgtohhmpdhrtghpthhtoh eprhgvphhnohhpsehgohhoghhlvgdrtghomhdprhgtphhtthhopehlihhnuhigqdhfshgu vghvvghlsehvghgvrhdrkhgvrhhnvghlrdhorhhgpdhrtghpthhtohepthihtghhohesth ihtghhohdrphhiiiiirg X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:16:20 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 1/9] Define the supervisor and event structure Date: Tue, 4 Mar 2025 01:12:57 +0000 Message-ID: X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 In the current design (mostly not implemented yet), a "supervisor" is a program that creates (but probably not enforce on itself) a Landlock ruleset which it specifically marks as operating in "supervise" mode. For such a layer (but not other layers below or above it), access not granted by the ruleset, which would normally result in a denial, instead triggers a supervise event, and the thread which caused the event is paused until either the supervisor responds to the event, the event is cancelled due to supervisor termination, or the requesting thread being killed. We define a refcounted structure that represents a supervisor, and will later be exposed to the user-space via a file descriptor. Each supervisor has an event queue and a separate list of events which have been read by the supervisor and is now awaiting response. This allows the future read codepath to not have to iterate over already notified events, but still allow the response codepath to find the event. The event struct is also refcounted, so that it is not tied to the lifetime of the supervisor (e.g. if it dies, the task doing the access that is currently stuck in kernel syscall still holds the event refcount, and can read its status safely). The details of the event structure will be populated in a future patch. The struct is called landlock_supervise_event_kernel so that the uapi header can use the shorter name. Signed-off-by: Tingmao Wang --- security/landlock/Makefile | 2 +- security/landlock/supervise.c | 72 +++++++++++++++++++++++++++++++++++ security/landlock/supervise.h | 63 ++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 security/landlock/supervise.c create mode 100644 security/landlock/supervise.h diff --git a/security/landlock/Makefile b/security/landlock/Makefile index b4538b7cf7d2..c9bab22ab0f5 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -1,6 +1,6 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o landlock-y := setup.o syscalls.o object.o ruleset.o \ - cred.o task.o fs.o + cred.o task.o fs.o supervise.o landlock-$(CONFIG_INET) += net.o diff --git a/security/landlock/supervise.c b/security/landlock/supervise.c new file mode 100644 index 000000000000..a3bb6928f453 --- /dev/null +++ b/security/landlock/supervise.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock LSM - Implementation specific to landlock-supervise + * + * Copyright © 2025 Tingmao Wang + */ + +#include +#include +#include +#include + +#include "supervise.h" + +struct landlock_supervisor *landlock_create_supervisor(void) +{ + struct landlock_supervisor *supervisor; + + supervisor = kzalloc(sizeof(*supervisor), GFP_KERNEL_ACCOUNT); + if (!supervisor) + return ERR_PTR(-ENOMEM); + refcount_set(&supervisor->usage, 1); + supervisor->next_event_id = 1; + spin_lock_init(&supervisor->lock); + INIT_LIST_HEAD(&supervisor->event_queue); + INIT_LIST_HEAD(&supervisor->notified_events); + init_waitqueue_head(&supervisor->poll_event_wq); + return supervisor; +} + +void landlock_get_supervisor(struct landlock_supervisor *const supervisor) +{ + refcount_inc(&supervisor->usage); +} + +static void +deny_and_put_event(struct landlock_supervise_event_kernel *const event) +{ + cmpxchg(&event->state, LANDLOCK_SUPERVISE_EVENT_NEW, + LANDLOCK_SUPERVISE_EVENT_DENIED); + cmpxchg(&event->state, LANDLOCK_SUPERVISE_EVENT_NOTIFIED, + LANDLOCK_SUPERVISE_EVENT_DENIED); + wake_up_var(event); + landlock_put_supervise_event(event); +} + +void landlock_put_supervisor(struct landlock_supervisor *const supervisor) +{ + if (refcount_dec_and_test(&supervisor->usage)) { + struct landlock_supervise_event_kernel *freeme, *next; + + might_sleep(); + /* we are the only reference, hence no locking */ + + /* deny all pending events */ + list_for_each_entry_safe(freeme, next, &supervisor->event_queue, + node) { + list_del(&freeme->node); + deny_and_put_event(freeme); + } + /* + * user reply no longer possible without any reference to + * supervisor, deny all notified events + */ + list_for_each_entry_safe(freeme, next, + &supervisor->notified_events, node) { + list_del(&freeme->node); + deny_and_put_event(freeme); + } + kfree(supervisor); + } +} diff --git a/security/landlock/supervise.h b/security/landlock/supervise.h new file mode 100644 index 000000000000..1fc3460335af --- /dev/null +++ b/security/landlock/supervise.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock LSM - Implementation specific to landlock-supervise + * + * Copyright © 2025 Tingmao Wang + */ + +#ifndef _SECURITY_LANDLOCK_SUPERVISE_H +#define _SECURITY_LANDLOCK_SUPERVISE_H + +#include +#include +#include +#include + +#include "access.h" +#include "ruleset.h" + +struct landlock_supervisor { + refcount_t usage; + spinlock_t lock; + /* protected by @lock, contains landlock_supervise_event_kernel */ + struct list_head event_queue; + /* protected by @lock, contains landlock_supervise_event_kernel */ + struct list_head notified_events; + struct wait_queue_head poll_event_wq; + /* protected by @lock */ + u32 next_event_id; +}; + +enum landlock_supervise_event_state { + LANDLOCK_SUPERVISE_EVENT_NEW, + LANDLOCK_SUPERVISE_EVENT_NOTIFIED, + LANDLOCK_SUPERVISE_EVENT_ALLOWED, + LANDLOCK_SUPERVISE_EVENT_DENIED, +}; + +struct landlock_supervise_event_kernel { + struct list_head node; + refcount_t usage; + enum landlock_supervise_event_state state; + + /* more fields to come */ +}; + +struct landlock_supervisor *landlock_create_supervisor(void); +void landlock_get_supervisor(struct landlock_supervisor *const supervisor); +void landlock_put_supervisor(struct landlock_supervisor *const supervisor); + +static inline void landlock_get_supervise_event( + struct landlock_supervise_event_kernel *const event) +{ + refcount_inc(&event->usage); +} + +static inline void landlock_put_supervise_event( + struct landlock_supervise_event_kernel *const event) +{ + if (refcount_dec_and_test(&event->usage)) + kfree(event); +} + +#endif /* _SECURITY_LANDLOCK_SUPERVISE_H */ From patchwork Tue Mar 4 01:12:58 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999743 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A1C66522A; Tue, 4 Mar 2025 01:17:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051079; cv=none; b=MMnxPXpMxqTliY9rsCSEkEUoSilyMLQuy3+ABchnVPGDxyVHknEaP0063C0D7K/F2pOLZbuOPtLeHeuhFtdAlTVMbWuEVVfwOb9NGrEZog7JqZgtBuBihRBF7l5skoV0HeYd9Eo0bg+W7r5XtcTxus/1pHMg2hQScJYOLVSqj1k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051079; c=relaxed/simple; bh=Lr+waHw4jFVtcAE2pAZcNuRcSpdzE010dYUZh3GrhY8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pWi/wXs5Dsz+yXHFpNgeCCYNShAGnSRhCH0PU6NKQB8AVTZhDzPzIzhqNkBwDGd4W1kETFi4DE2WFLNY7Oc9Tt/4WAYzzKonFKaEASGP7sXxuBv7ridUYlTSIlyDpcszljaFUlGuYq/E98v9oa8HNchzd0o0s4G5p0l2/kbIOZg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=hvPWAg2E; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=1ENKiGvD; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="hvPWAg2E"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="1ENKiGvD" Received: from phl-compute-12.internal (phl-compute-12.phl.internal [10.202.2.52]) by mailflow.stl.internal (Postfix) with ESMTP id 61F611D4157C; Mon, 3 Mar 2025 20:17:55 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-12.internal (MEProxy); Mon, 03 Mar 2025 20:17:55 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1741051075; x= 1741054675; bh=COU+tXy4ri0RR8vWI/vuxbIIyUcrjBzStMi1pCBq06Q=; b=h vPWAg2EhUev+yRHcOtnx6b4yrCkripkmN413JHF/hbC9Kb6tPxKSgKv2fLrko6Sm Iyi9wifdnFuJpr7xjSAzODfwNsn28gLuKPQ8jcNSyfXLsHlWlrlDF+ysViwghc7x MgkbcsJrD3JGHWebik5JdNg/QvCI6e+2m0ba0bQzeK8+5yvqduomBUUFFvSNWUmj 8Baag8QMr35wc/HKOZk16Rnchox/i7hFQe9HoKBfUrrI1PgCJ5jpOIm9ICcG6pDF 1pbxOapq7DjFLdsff1evk6IsHaJ7SlFrj50+AZM2GO7fOWGm2O/sYtYpBarKMfV0 Ck4467p8Yg2mwAmE90pNg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1741051075; x=1741054675; bh=C OU+tXy4ri0RR8vWI/vuxbIIyUcrjBzStMi1pCBq06Q=; b=1ENKiGvD9DOMwvK4e 1xcfe1WgkIwas/t66E3+20uBXWjrGtsMw0nSC7cX8xvwo1+Mnr1IzgPjssIszGfU dIHN+Ctr3ENFeFXnCpEocHpy4MgfU3CGuhdjc2g0OdW0hvQBKlZMTPauJxEZ68CC BN3hppUa916ppafuPvaStYAQX98ieBYG/xVtHKA+3EbwLoRJp+aPnn7lBkwEusbr fJ3bY8bWO4eaxL59VD4ho+7UdxFYugf6GJryRHBtlLQioNypRAyLZJ27LYOctI2q kjOGLGIUxLmKWUCt3V6BadYOlzJ9Yn4vQSOxRzL+EaXWznyhhLoWiI5trYsT/PF6 L9J+w== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieejucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhgggfestdekredtredt tdenucfhrhhomhepvfhinhhgmhgrohcuhggrnhhguceomhesmhgrohifthhmrdhorhhgqe enucggtffrrghtthgvrhhnpeeiteekudejheegkeekgfehueekuddtgfdtgedukeeufeev veffudfgffevvefghfenucffohhmrghinheplhgrhigvrhgplhgvvhgvlhdrnhgvthdprg gttggvshhspghmrghskhhsrdhnvghtnecuvehluhhsthgvrhfuihiivgeptdenucfrrghr rghmpehmrghilhhfrhhomhepmhesmhgrohifthhmrdhorhhgpdhnsggprhgtphhtthhope elpdhmohguvgepshhmthhpohhuthdprhgtphhtthhopehmihgtseguihhgihhkohgurdhn vghtpdhrtghpthhtohepghhnohgrtghksehgohhoghhlvgdrtghomhdprhgtphhtthhope hjrggtkhesshhushgvrdgtiidprhgtphhtthhopehmsehmrghofihtmhdrohhrghdprhgt phhtthhopehlihhnuhigqdhsvggtuhhrihhthidqmhhoughulhgvsehvghgvrhdrkhgvrh hnvghlrdhorhhgpdhrtghpthhtoheprghmihhrjeefihhlsehgmhgrihhlrdgtohhmpdhr tghpthhtoheprhgvphhnohhpsehgohhoghhlvgdrtghomhdprhgtphhtthhopehlihhnuh igqdhfshguvghvvghlsehvghgvrhdrkhgvrhhnvghlrdhorhhgpdhrtghpthhtohepthih tghhohesthihtghhohdrphhiiiiirg X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:17:53 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 2/9] Refactor per-layer information in rulesets and rules Date: Tue, 4 Mar 2025 01:12:58 +0000 Message-ID: <6e8887f204c9fbe7470e61876bc597932a8f74d9.1741047969.git.m@maowtm.org> X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 We need a place to store the supervisor pointer for each layer in a domain. Currently, the domain has a trailing flexible array for handled access masks of each layer. This patch extends it by creating a separate landlock_ruleset_layer structure that will hold this access mask, and make the ruleset's flexible array use this structure instead. An alternative is to use landlock_hierarchy, but I have chosen to extend the FAM as this is makes it more clear the supervisor pointer is tied to layers, just like access masks. This patch doesn't make any functional changes nor add any supervise specific stuff. It is purely to pave the way for future patches. Signed-off-by: Tingmao Wang --- security/landlock/ruleset.c | 29 +++++++++--------- security/landlock/ruleset.h | 59 ++++++++++++++++++++++-------------- security/landlock/syscalls.c | 2 +- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 69742467a0cf..2cc6f7c5eb1b 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -31,9 +31,8 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) { struct landlock_ruleset *new_ruleset; - new_ruleset = - kzalloc(struct_size(new_ruleset, access_masks, num_layers), - GFP_KERNEL_ACCOUNT); + new_ruleset = kzalloc(struct_size(new_ruleset, layer_stack, num_layers), + GFP_KERNEL_ACCOUNT); if (!new_ruleset) return ERR_PTR(-ENOMEM); refcount_set(&new_ruleset->usage, 1); @@ -104,8 +103,9 @@ static bool is_object_pointer(const enum landlock_key_type key_type) static struct landlock_rule * create_rule(const struct landlock_id id, - const struct landlock_layer (*const layers)[], const u32 num_layers, - const struct landlock_layer *const new_layer) + const struct landlock_rule_layer (*const layers)[], + const u32 num_layers, + const struct landlock_rule_layer *const new_layer) { struct landlock_rule *new_rule; u32 new_num_layers; @@ -201,7 +201,7 @@ static void build_check_ruleset(void) */ static int insert_rule(struct landlock_ruleset *const ruleset, const struct landlock_id id, - const struct landlock_layer (*const layers)[], + const struct landlock_rule_layer (*const layers)[], const size_t num_layers) { struct rb_node **walker_node; @@ -284,7 +284,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset, static void build_check_layer(void) { - const struct landlock_layer layer = { + const struct landlock_rule_layer layer = { .level = ~0, .access = ~0, }; @@ -299,7 +299,7 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, const struct landlock_id id, const access_mask_t access) { - struct landlock_layer layers[] = { { + struct landlock_rule_layer layers[] = { { .access = access, /* When @level is zero, insert_rule() extends @ruleset. */ .level = 0, @@ -344,7 +344,7 @@ static int merge_tree(struct landlock_ruleset *const dst, /* Merges the @src tree. */ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root, node) { - struct landlock_layer layers[] = { { + struct landlock_rule_layer layers[] = { { .level = dst->num_layers, } }; const struct landlock_id id = { @@ -389,8 +389,9 @@ static int merge_ruleset(struct landlock_ruleset *const dst, err = -EINVAL; goto out_unlock; } - dst->access_masks[dst->num_layers - 1] = - landlock_upgrade_handled_access_masks(src->access_masks[0]); + dst->layer_stack[dst->num_layers - 1].access_masks = + landlock_upgrade_handled_access_masks( + src->layer_stack[0].access_masks); /* Merges the @src inode tree. */ err = merge_tree(dst, src, LANDLOCK_KEY_INODE); @@ -472,8 +473,8 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, goto out_unlock; } /* Copies the parent layer stack and leaves a space for the new layer. */ - memcpy(child->access_masks, parent->access_masks, - flex_array_size(parent, access_masks, parent->num_layers)); + memcpy(child->layer_stack, parent->layer_stack, + flex_array_size(parent, layer_stack, parent->num_layers)); if (WARN_ON_ONCE(!parent->hierarchy)) { err = -EINVAL; @@ -644,7 +645,7 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule, * E.g. /a/b + /a => /a/b */ for (layer_level = 0; layer_level < rule->num_layers; layer_level++) { - const struct landlock_layer *const layer = + const struct landlock_rule_layer *const layer = &rule->layers[layer_level]; const layer_mask_t layer_bit = BIT_ULL(layer->level - 1); const unsigned long access_req = access_request; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 52f4f0af6ab0..a2605959f733 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -21,9 +21,10 @@ #include "object.h" /** - * struct landlock_layer - Access rights for a given layer + * struct landlock_rule_layer - Stores the access rights for a + * given layer in a rule. */ -struct landlock_layer { +struct landlock_rule_layer { /** * @level: Position of this layer in the layer stack. */ @@ -102,10 +103,11 @@ struct landlock_rule { */ u32 num_layers; /** - * @layers: Stack of layers, from the latest to the newest, implemented - * as a flexible array member (FAM). + * @layers: Stack of layers, from the latest to the newest, + * implemented as a flexible array member (FAM). Only + * contains layers that has a rule for this object. */ - struct landlock_layer layers[] __counted_by(num_layers); + struct landlock_rule_layer layers[] __counted_by(num_layers); }; /** @@ -124,6 +126,18 @@ struct landlock_hierarchy { refcount_t usage; }; +/** + * struct landlock_ruleset_layer - Store per-layer information + * within a domain (or a non-merged ruleset) + */ +struct landlock_ruleset_layer { + /** + * @access_masks: Contains the subset of filesystem and + * network actions that are restricted by a layer. + */ + struct access_masks access_masks; +}; + /** * struct landlock_ruleset - Landlock ruleset * @@ -187,18 +201,17 @@ struct landlock_ruleset { */ u32 num_layers; /** - * @access_masks: Contains the subset of filesystem and - * network actions that are restricted by a ruleset. - * A domain saves all layers of merged rulesets in a - * stack (FAM), starting from the first layer to the - * last one. These layers are used when merging - * rulesets, for user space backward compatibility - * (i.e. future-proof), and to properly handle merged - * rulesets without overlapping access rights. These - * layers are set once and never changed for the - * lifetime of the ruleset. + * @layer_stack: A domain saves all layers of merged + * rulesets in a stack (FAM), starting from the first + * layer to the last one. These layers are used when + * merging rulesets, for user space backward + * compatibility (i.e. future-proof), and to properly + * handle merged rulesets without overlapping access + * rights. These layers are set once and never + * changed for the lifetime of the ruleset. */ - struct access_masks access_masks[]; + struct landlock_ruleset_layer + layer_stack[] __counted_by(num_layers); }; }; }; @@ -248,7 +261,7 @@ landlock_union_access_masks(const struct landlock_ruleset *const domain) for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { union access_masks_all layer = { - .masks = domain->access_masks[layer_level], + .masks = domain->layer_stack[layer_level].access_masks, }; matches.all |= layer.all; @@ -296,7 +309,7 @@ landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset, /* Should already be checked in sys_landlock_create_ruleset(). */ WARN_ON_ONCE(fs_access_mask != fs_mask); - ruleset->access_masks[layer_level].fs |= fs_mask; + ruleset->layer_stack[layer_level].access_masks.fs |= fs_mask; } static inline void @@ -308,7 +321,7 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset, /* Should already be checked in sys_landlock_create_ruleset(). */ WARN_ON_ONCE(net_access_mask != net_mask); - ruleset->access_masks[layer_level].net |= net_mask; + ruleset->layer_stack[layer_level].access_masks.net |= net_mask; } static inline void @@ -319,7 +332,7 @@ landlock_add_scope_mask(struct landlock_ruleset *const ruleset, /* Should already be checked in sys_landlock_create_ruleset(). */ WARN_ON_ONCE(scope_mask != mask); - ruleset->access_masks[layer_level].scope |= mask; + ruleset->layer_stack[layer_level].access_masks.scope |= mask; } static inline access_mask_t @@ -327,7 +340,7 @@ landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset, const u16 layer_level) { /* Handles all initially denied by default access rights. */ - return ruleset->access_masks[layer_level].fs | + return ruleset->layer_stack[layer_level].access_masks.fs | _LANDLOCK_ACCESS_FS_INITIALLY_DENIED; } @@ -335,14 +348,14 @@ static inline access_mask_t landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset, const u16 layer_level) { - return ruleset->access_masks[layer_level].net; + return ruleset->layer_stack[layer_level].access_masks.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].scope; + return ruleset->layer_stack[layer_level].access_masks.scope; } bool landlock_unmask_layers(const struct landlock_rule *const rule, diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index a9760d252fc2..ead9b68168ad 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -313,7 +313,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, return -ENOMSG; /* Checks that allowed_access matches the @ruleset constraints. */ - mask = ruleset->access_masks[0].fs; + mask = landlock_get_fs_access_mask(ruleset, 0); if ((path_beneath_attr.allowed_access | mask) != mask) return -EINVAL; From patchwork Tue Mar 4 01:12:59 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999744 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B8AF533D8; Tue, 4 Mar 2025 01:18:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051124; cv=none; b=qYbu/rhUO4KiHGCTJg2uAbeKT4UcFIR191NORoHHwCpl9frtdzxHLQvBoQnkgfJVy2y9wINJBpumWvKrf6PfxwmSkNmpdRIzXmOyjcShzivbp8ZSPI6esFnydHgP49KA/oK03nHz3OWfVQtrlzMX3pAEAqRoukt7TqpZ3C9AS7g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051124; c=relaxed/simple; bh=dSH9kUOUWHaJDnK5KJYcUvCKQpT16cRy0YmC870m9I8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YwvdyiSoBYj2opgVsrvUTFKVHBMIDsYrsl0t/CxJfdeuWwSpR+oCMJvcoyGb6hHovI+Wcom3Rc6AY7+o+nZBL3YlumSfO6BFskPi0Hb7WA4osMmu5sXLKw5skFkriUP2I9HBlf+DiB4Jny2oq2gCC0aoFSVII68zyvW7LeLOwfI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=hlqbxyIW; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=w9qZSNFN; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="hlqbxyIW"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="w9qZSNFN" Received: from phl-compute-01.internal (phl-compute-01.phl.internal [10.202.2.41]) by mailflow.stl.internal (Postfix) with ESMTP id A1D851D415B6; Mon, 3 Mar 2025 20:18:40 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-01.internal (MEProxy); Mon, 03 Mar 2025 20:18:40 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1741051120; x= 1741054720; bh=WgrkeV/hKLUg1ORoyHWD4+MCv5x+yzOiUtNRUl1NE8E=; b=h lqbxyIW35l0xK+lI/A3cEX2k946K3/grHC2EEUzyKi16ia32TFBbPpqOMMbJg5H6 gNrjZ5FLrYhWpB6fGG6Q9MIs5u01pBOsWtpq840Cuo4BFzycIR5F3zSdyZCBj/rK cgh4XdWX58BgWZdE+qhbXjDvhcIiDJH0vcEUVsAmW8a9TVvunO2YWhrb8XDcp6db Bz838WSItHCJgVNoyqtpljR8AoBXlh0SB8OMWwouK5GkBjUHPnBSUTpqFUwY9L26 Z1iT5322eSdg6i4dPvGX/pH+wYQur2WVAJLtG9Z2IzsFKGBGzIP6Byi/z701SzLe qOH9DQbT7MhrxeNNq/nag== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1741051120; x=1741054720; bh=W grkeV/hKLUg1ORoyHWD4+MCv5x+yzOiUtNRUl1NE8E=; b=w9qZSNFN2aUVybO86 jhmHDZzInGb7OI3A5wnfyyDHXmp0u1OO9jxDidjCiFiNuizn11PZlDIzVSFXPLP7 caW1GVng3+sFZ2x1zurZAxsmxzfytydFitE8aFt8PvM5MGxbQNUzg/G89iQyMKOR LDSghUrU91LrV5q0A15JGqy2wkF34cOkbBYwRJlQY/8A5fzpBNBuhfC7Q+yVlJYF NuvK8R0Cv8YVucIMuff3eiINKX+e6p0/gFbOoGSsgGlpcpa4JWCWOx6rjQvlEqO1 INnpg5ZpTRF5Tpz3uA28OmlVTDoME0JR6mxvBitkl6jw9W/K1W5FIUNG2FnK3xyj JBovQ== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieejucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhgggfestdekredtredt tdenucfhrhhomhepvfhinhhgmhgrohcuhggrnhhguceomhesmhgrohifthhmrdhorhhgqe enucggtffrrghtthgvrhhnpeeuuddthefhhefhvdejteevvddvteefffegteetueegueel jeefueekjeetieeuleenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehmsehmrghofihtmhdrohhrghdpnhgspghrtghpthhtohepledpmhhouggv pehsmhhtphhouhhtpdhrtghpthhtohepmhhitgesughighhikhhougdrnhgvthdprhgtph htthhopehgnhhorggtkhesghhoohhglhgvrdgtohhmpdhrtghpthhtohepjhgrtghksehs uhhsvgdrtgiipdhrtghpthhtohepmhesmhgrohifthhmrdhorhhgpdhrtghpthhtoheplh hinhhugidqshgvtghurhhithihqdhmohguuhhlvgesvhhgvghrrdhkvghrnhgvlhdrohhr ghdprhgtphhtthhopegrmhhirhejfehilhesghhmrghilhdrtghomhdprhgtphhtthhope hrvghpnhhophesghhoohhglhgvrdgtohhmpdhrtghpthhtoheplhhinhhugidqfhhsuggv vhgvlhesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehthigthhhosehthi gthhhordhpihiiiigr X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:18:38 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 3/9] Adds a supervisor reference in the per-layer information Date: Tue, 4 Mar 2025 01:12:59 +0000 Message-ID: <2b212f28b30675836e75d0ac70868fe48c0773e0.1741047969.git.m@maowtm.org> X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Following from the previous patch, we now use the new per-layer struct to store a reference to any supervisor attached to a layer (merged in a domain or unmerged). The supervisor is refcounted, and so we need to correctly get/put it when inheriting a domain or when merging a layer. This means looping through all the layers and getting each supervisor that exists, as the domain effectively stores a copy of all the inherited layers. TODO: because we are now referencing the supervisor in the layer, the event deny and cleanup code in landlock_put_supervisor won't work as intended. I didn't realize this until after finishing this set of patches, so this will be addressed in a future series. Signed-off-by: Tingmao Wang --- security/landlock/ruleset.c | 26 +++++++++++++++++++++++--- security/landlock/ruleset.h | 7 +++++++ security/landlock/supervise.h | 6 ++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 2cc6f7c5eb1b..2e93b8105cc9 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -26,6 +26,7 @@ #include "limits.h" #include "object.h" #include "ruleset.h" +#include "supervise.h" static struct landlock_ruleset *create_ruleset(const u32 num_layers) { @@ -389,9 +390,14 @@ static int merge_ruleset(struct landlock_ruleset *const dst, err = -EINVAL; goto out_unlock; } - dst->layer_stack[dst->num_layers - 1].access_masks = - landlock_upgrade_handled_access_masks( - src->layer_stack[0].access_masks); + dst->layer_stack[dst->num_layers - 1] = (struct landlock_ruleset_layer){ + .access_masks = landlock_upgrade_handled_access_masks( + src->layer_stack[0].access_masks), + .supervisor = src->layer_stack[0].supervisor, + }; + if (dst->layer_stack[dst->num_layers - 1].supervisor) + landlock_get_supervisor( + dst->layer_stack[dst->num_layers - 1].supervisor); /* Merges the @src inode tree. */ err = merge_tree(dst, src, LANDLOCK_KEY_INODE); @@ -447,6 +453,7 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, struct landlock_ruleset *const child) { int err = 0; + int layer; might_sleep(); if (!parent) @@ -475,6 +482,12 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, /* Copies the parent layer stack and leaves a space for the new layer. */ memcpy(child->layer_stack, parent->layer_stack, flex_array_size(parent, layer_stack, parent->num_layers)); + /* Get the refcount of any supervisor copied over */ + for (layer = 0; layer < child->num_layers; layer++) { + if (child->layer_stack[layer].supervisor) + landlock_get_supervisor( + child->layer_stack[layer].supervisor); + } if (WARN_ON_ONCE(!parent->hierarchy)) { err = -EINVAL; @@ -492,6 +505,7 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, static void free_ruleset(struct landlock_ruleset *const ruleset) { struct landlock_rule *freeme, *next; + int layer; might_sleep(); rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode, @@ -505,6 +519,12 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) #endif /* IS_ENABLED(CONFIG_INET) */ put_hierarchy(ruleset->hierarchy); + for (layer = 0; layer < ruleset->num_layers; layer++) { + struct landlock_supervisor *const supervisor = + ruleset->layer_stack[layer].supervisor; + if (supervisor) + landlock_put_supervisor(supervisor); + } kfree(ruleset); } diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index a2605959f733..ed530643ea68 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -136,6 +136,13 @@ struct landlock_ruleset_layer { * network actions that are restricted by a layer. */ struct access_masks access_masks; + /** + * @supervisor: If not null, this layer is operating in + * supervisor mode. Access denied by only supervised layers + * are forwarded to the supervisor(s), who can then make a + * decision whether to actually deny the access, or allow it. + */ + struct landlock_supervisor *supervisor; }; /** diff --git a/security/landlock/supervise.h b/security/landlock/supervise.h index 1fc3460335af..febe26a11578 100644 --- a/security/landlock/supervise.h +++ b/security/landlock/supervise.h @@ -16,6 +16,12 @@ #include "access.h" #include "ruleset.h" +/** + * Each supervisor is associated with one active layer in a + * domain (or associated with a not-yet-active layer in a struct + * landlock_ruleset). User-space interact with the event queue + * through a landlock_supervise_fd. + */ struct landlock_supervisor { refcount_t usage; spinlock_t lock; From patchwork Tue Mar 4 01:13:00 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999745 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 877E38494; Tue, 4 Mar 2025 01:19:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051169; cv=none; b=lmhglZvqo+uZ1JBMCWuFBqT4wGwlWWrbLmJi0EGOJ8qSh1xMV+p0BU40KXF3kSA6Z+ka3F3yk6GSUe+q9o+ez+Ojfm5W2rbTQWzz0DcBP/O40dl31NQhhwzWUmHyi79tG62CoxhfBeceC8p1UkJZDqgmxM9oqB2evpY0bmRZv5g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051169; c=relaxed/simple; bh=07ti56OF70Rg82aq8oC1MBeYhwa+T+Bz0OBSbhzm2vM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=r8gQurrqXFOnT5i7Jzz5fi4pQ3NTvbEnN6ybmHyHcKQcPQYGC3mwY8tTns3Hbw+eWBtdC7Li1KI+vFiR3mLwJuh6RyuxvCelgqDdpa4cgCrnVzrLDP1QfSN38jP2J9b7j4RFM4mk+QUqyd4OfLMOVLCrHOrqp7987KX4PFHXNLQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=hBPxXWfu; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=XYscuKvA; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="hBPxXWfu"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="XYscuKvA" Received: from phl-compute-09.internal (phl-compute-09.phl.internal [10.202.2.49]) by mailflow.stl.internal (Postfix) with ESMTP id 4A3E41D415DB; Mon, 3 Mar 2025 20:19:26 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-09.internal (MEProxy); Mon, 03 Mar 2025 20:19:26 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1741051166; x= 1741054766; bh=9bKXC+Rkl+qdQICNs4xhf8d4SSP5mCAePj8E00/EBdc=; b=h BPxXWfuFGrIKMLbfSMc29N+Y8i0A22P/WFQQVPwI7NLzPBBpuU/3DtQWarBVaRyT IqAEKt1pJ5ZSFw1W8nEGyH5DKGxCRuR2KXclgci/AlVhyWOcW+6cioz56M8AiKma GD2zFErL5UH7OUcCFNfOCYJO9USVsvZXuDqepBB6IPY993/pIuzEOIaeOKlI5blV BtW9FFAUedoRDXzirmwZvn/08PPUba+DwIHwcr0VyQyAOdvQD5uiKd62f54d4Iia nO+QSa3qaTUr26rbcNVtR4Uv+Z6pHOl6qf0wlZQul8G23Hoj24i9PoBLcCvgl+MK 4tKpmLIop7m6zdidrTLjQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1741051166; x=1741054766; bh=9 bKXC+Rkl+qdQICNs4xhf8d4SSP5mCAePj8E00/EBdc=; b=XYscuKvAGIPDwcwHp W3kaGtNhcAzP/RkIdzOUxANz7aD0Vo1JeUxjzQmBzxGT66wPq/2ktvP03zRxlifP bkhU7vv3iZ65aNm6p1E30merCZGvWrfumrs8ZYrcxeMzIe54Mk02XWr9896LZAA7 i1fl4Cm39iSzN0cYx5TgEbJn9lnz46xFti3VuxzDZYyt+4IYYJCAAp1OfcML1ehk VVZ1nEwmTFPjBN8/kznaL/SZ9/KueX4cp8JOotD1bSugH65PTQBfn/1G4yqH530j g6h3AeRokoUnPZgTrZuVSwfkwVvyvL7VagzQO5xuEmK9Yh+X9k9/W8di/AsTEl7J 7hfLA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieekucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhgggfestdekredtredt tdenucfhrhhomhepvfhinhhgmhgrohcuhggrnhhguceomhesmhgrohifthhmrdhorhhgqe enucggtffrrghtthgvrhhnpeeuuddthefhhefhvdejteevvddvteefffegteetueegueel jeefueekjeetieeuleenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehmsehmrghofihtmhdrohhrghdpnhgspghrtghpthhtohepledpmhhouggv pehsmhhtphhouhhtpdhrtghpthhtohepmhhitgesughighhikhhougdrnhgvthdprhgtph htthhopehgnhhorggtkhesghhoohhglhgvrdgtohhmpdhrtghpthhtohepjhgrtghksehs uhhsvgdrtgiipdhrtghpthhtohepmhesmhgrohifthhmrdhorhhgpdhrtghpthhtoheplh hinhhugidqshgvtghurhhithihqdhmohguuhhlvgesvhhgvghrrdhkvghrnhgvlhdrohhr ghdprhgtphhtthhopegrmhhirhejfehilhesghhmrghilhdrtghomhdprhgtphhtthhope hrvghpnhhophesghhoohhglhgvrdgtohhmpdhrtghpthhtoheplhhinhhugidqfhhsuggv vhgvlhesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehthigthhhosehthi gthhhordhpihiiiigr X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:19:24 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 4/9] User-space API for creating a supervisor-fd Date: Tue, 4 Mar 2025 01:13:00 +0000 Message-ID: <03d822634936f4c3ac8e4843f9913d1b1fa9d081.1741047969.git.m@maowtm.org> X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 We allow the user to pass in an additional flag to landlock_create_ruleset which will make the ruleset operate in "supervise" mode, with a supervisor attached. We create additional space in the landlock_ruleset_attr structure to pass the newly created supervisor fd back to user-space. The intention, while not implemented yet, is that the user-space will read events from this fd and write responses back to it. Note: need to investigate if fd clone on fork() is handled correctly, but should be fine if it shares the struct file. We might also want to let the user customize the flags on this fd, so that they can request no O_CLOEXEC. NOTE: despite this patch having a new uapi, I'm still very open to e.g. re-using fanotify stuff instead (if that makes sense in the end). This is just a PoC. Signed-off-by: Tingmao Wang --- include/uapi/linux/landlock.h | 10 ++++ security/landlock/syscalls.c | 102 +++++++++++++++++++++++++++++----- 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index e1d2c27533b4..7bc1eb4859fb 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -50,6 +50,15 @@ struct landlock_ruleset_attr { * resources (e.g. IPCs). */ __u64 scoped; + /** + * @supervisor_fd: Placeholder to store the supervisor file + * descriptor when %LANDLOCK_CREATE_RULESET_SUPERVISE is set. + */ + __s32 supervisor_fd; + /** + * @pad: Unused, must be zero. + */ + __u32 pad; }; /* @@ -60,6 +69,7 @@ struct landlock_ruleset_attr { */ /* clang-format off */ #define LANDLOCK_CREATE_RULESET_VERSION (1U << 0) +#define LANDLOCK_CREATE_RULESET_SUPERVISE (1U << 1) /* clang-format on */ /** diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index ead9b68168ad..adf7e77023b5 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -32,6 +32,7 @@ #include "limits.h" #include "net.h" #include "ruleset.h" +#include "supervise.h" #include "setup.h" static bool is_initialized(void) @@ -99,8 +100,10 @@ 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); + ruleset_size += sizeof(ruleset_attr.supervisor_fd); + ruleset_size += sizeof(ruleset_attr.pad); BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); - BUILD_BUG_ON(sizeof(ruleset_attr) != 24); + BUILD_BUG_ON(sizeof(ruleset_attr) != 32); path_beneath_size = sizeof(path_beneath_attr.allowed_access); path_beneath_size += sizeof(path_beneath_attr.parent_fd); @@ -151,16 +154,42 @@ static const struct file_operations ruleset_fops = { .write = fop_dummy_write, }; -#define LANDLOCK_ABI_VERSION 6 +static int fop_supervisor_release(struct inode *const inode, + struct file *const filp) +{ + struct landlock_supervisor *supervisor = filp->private_data; + + landlock_put_supervisor(supervisor); + return 0; +} + +static const struct file_operations supervisor_fops = { + .release = fop_supervisor_release, + /* TODO: read, write, poll, dup */ + .read = fop_dummy_read, + .write = fop_dummy_write, +}; + +static int +landlock_supervisor_open_fd(struct landlock_supervisor *const supervisor, + const fmode_t mode) +{ + landlock_get_supervisor(supervisor); + return anon_inode_getfd("[landlock-supervisor]", &supervisor_fops, + supervisor, O_RDWR | O_CLOEXEC); +} + +#define LANDLOCK_ABI_VERSION 7 /** * sys_landlock_create_ruleset - Create a new ruleset * - * @attr: Pointer to a &struct landlock_ruleset_attr identifying the scope of - * the new ruleset. - * @size: Size of the pointed &struct landlock_ruleset_attr (needed for - * backward and forward compatibility). - * @flags: Supported value: %LANDLOCK_CREATE_RULESET_VERSION. + * @attr: Pointer to a &struct landlock_ruleset_attr identifying the scope of + * the new ruleset. + * @size: Size of the pointed &struct landlock_ruleset_attr (needed for + * backward and forward compatibility). + * @flags: Supported value: %LANDLOCK_CREATE_RULESET_VERSION, + * %LANDLOCK_CREATE_RULESET_SUPERVISE. * * This system call enables to create a new Landlock ruleset, and returns the * related file descriptor on success. @@ -172,18 +201,21 @@ 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 unknown scope, or too small @size; + * - %EINVAL: unknown @flags, or unknown access, or unknown + * scope, or too small @size, or non-zero @pad; * - %E2BIG: @attr or @size inconsistencies; * - %EFAULT: @attr or @size inconsistencies; * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. */ SYSCALL_DEFINE3(landlock_create_ruleset, - const struct landlock_ruleset_attr __user *const, attr, - const size_t, size, const __u32, flags) + struct landlock_ruleset_attr __user *const, attr, const size_t, + size, const __u32, flags) { struct landlock_ruleset_attr ruleset_attr; struct landlock_ruleset *ruleset; + struct landlock_supervisor *supervisor; int err, ruleset_fd; + bool supervise = false; /* Build-time checks. */ build_check_abi(); @@ -192,10 +224,16 @@ SYSCALL_DEFINE3(landlock_create_ruleset, return -EOPNOTSUPP; if (flags) { - if ((flags == LANDLOCK_CREATE_RULESET_VERSION) && !attr && - !size) + if (flags == LANDLOCK_CREATE_RULESET_VERSION) { + if (attr || size) + return -EINVAL; return LANDLOCK_ABI_VERSION; - return -EINVAL; + } + if (flags == LANDLOCK_CREATE_RULESET_SUPERVISE) { + supervise = true; + } else { + return -EINVAL; + } } /* Copies raw user space buffer. */ @@ -206,6 +244,13 @@ SYSCALL_DEFINE3(landlock_create_ruleset, if (err) return err; + if (supervise && size < offsetofend(typeof(ruleset_attr), pad)) + return -EINVAL; + + if (size >= offsetofend(typeof(ruleset_attr), pad) && + ruleset_attr.pad != 0) + return -EINVAL; + /* Checks content (and 32-bits cast). */ if ((ruleset_attr.handled_access_fs | LANDLOCK_MASK_ACCESS_FS) != LANDLOCK_MASK_ACCESS_FS) @@ -227,11 +272,40 @@ SYSCALL_DEFINE3(landlock_create_ruleset, if (IS_ERR(ruleset)) return PTR_ERR(ruleset); + if (supervise) { + supervisor = landlock_create_supervisor(); + if (IS_ERR(supervisor)) { + landlock_put_ruleset(ruleset); + return -ENOMEM; + } + /* Pass ownership of supervisor to ruleset struct */ + ruleset->layer_stack[0].supervisor = supervisor; + } + /* Creates anonymous FD referring to the ruleset. */ ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops, ruleset, O_RDWR | O_CLOEXEC); - if (ruleset_fd < 0) + if (ruleset_fd < 0) { landlock_put_ruleset(ruleset); + return ruleset_fd; + } + + if (supervise) { + int supervisor_fd; + + supervisor_fd = landlock_supervisor_open_fd( + ruleset->layer_stack[0].supervisor, O_RDWR | O_CLOEXEC); + if (supervisor_fd < 0) { + landlock_put_ruleset(ruleset); + return supervisor_fd; + } + if (copy_to_user(&attr->supervisor_fd, &supervisor_fd, + sizeof(supervisor_fd))) { + landlock_put_ruleset(ruleset); + return -EFAULT; + } + } + return ruleset_fd; } From patchwork Tue Mar 4 01:13:01 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999746 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2E81E33D8; Tue, 4 Mar 2025 01:20:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051206; cv=none; b=gizVZCGoM2BZ30MqWfyYJKDmCE4zMS4KAHKaNbk7DNlTu/lofWB0ybmsOOLtpgcjw3bybYNrf3KnxwvUZ4Z6T4cXrrVleVbJEVg2L8hrUOnlnCvMS5KqAyIXGsHe9dm0jQR6LhPDVbO6PTIFmmLkWLb35doPxzPl/TL2eO0U/N0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051206; c=relaxed/simple; bh=/6zHEeNlWOk76fvKC+LZi+JAcOoQkyaG2HOS//r+sOE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=drhLkXNDHioJBGaQcdVsEr5AIlmvpNMq4/EbcG1fCPu+QU67TQGEu0a2Tvf8Z6ezSPP2nGPGUsBU/PgEaeRQcZl94xkPC2CzX0ii06O6bhExaCcPH3ReKQUomFgNIVHHD/wzGt6PIFKOQIMw+EwFQLRk5MgqflpTcMZ39Nv206E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=caWyo5Hf; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=1iIinpMM; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="caWyo5Hf"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="1iIinpMM" Received: from phl-compute-05.internal (phl-compute-05.phl.internal [10.202.2.45]) by mailflow.stl.internal (Postfix) with ESMTP id B5DA21D415DA; Mon, 3 Mar 2025 20:20:03 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-05.internal (MEProxy); Mon, 03 Mar 2025 20:20:03 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1741051203; x= 1741054803; bh=dJjmDT0TrggptoCsUZJlKhp/zUupXoZO1X3oOo6W7zU=; b=c aWyo5HfgJInzlqmlMSvctKnJPI4fSsqxx/LvhKr/xW3/S/82N81F58NwiEwd7nvU Mr4/fw1f70/7AelsugDrAbeiWD4rRrGHK0Cj9I8dOGQ7L8RCHy7IwwaD3N3d37a8 H8FGnWIFzB/SID9uYIZ1zlGLCuR/YDuRq0s96SkDWYLAYMnxkUCAaqVuOfJLnxIj VuTEQAehVLp+LLARMaY04JXSGTs6fEJ5gaAnnt1PbltkpzriwTIouxhWiTPHoMOT CMm+AL9XgnssppugivfVSiTMoNOsMGFquVZG7b1v1+75NeCtLHnfx9/TfbUj5hlY BKtTdlovJRCJ1nf72qRlw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1741051203; x=1741054803; bh=d JjmDT0TrggptoCsUZJlKhp/zUupXoZO1X3oOo6W7zU=; b=1iIinpMMlhSA+BDCv snHeVKMwGVg/LyzQhXL48UqBM3u8toYr092KS7zIMn5GkuvWVvs+xXOZEKebpl5p QAeqrbqpU9bUNvidcC8uX70S3L01sEStOIPJL9I49Hm+errBfgl8q8LBVXI8TAjh CDfgRXpbPfG+u9PfIf9+dwC1Husz6gjnKIwP3iIoG2ayOxhQMsN1PLmNpHQgbS7x kVsurGI8EUoT37isORC9tzBHV00a0JoJ+IchLZvcJww/fJCoWRt+z7riJrADWTkN UHMT9A9Uo7fOzM2NzHuJfgswXsmzhbkuc1xqAbRsV8sqy5a5Fq1gapDp+dAad63Q MX53g== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieejucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhgggfestdekredtredt tdenucfhrhhomhepvfhinhhgmhgrohcuhggrnhhguceomhesmhgrohifthhmrdhorhhgqe enucggtffrrghtthgvrhhnpeeuuddthefhhefhvdejteevvddvteefffegteetueegueel jeefueekjeetieeuleenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehmsehmrghofihtmhdrohhrghdpnhgspghrtghpthhtohepledpmhhouggv pehsmhhtphhouhhtpdhrtghpthhtohepmhhitgesughighhikhhougdrnhgvthdprhgtph htthhopehgnhhorggtkhesghhoohhglhgvrdgtohhmpdhrtghpthhtohepjhgrtghksehs uhhsvgdrtgiipdhrtghpthhtohepmhesmhgrohifthhmrdhorhhgpdhrtghpthhtoheplh hinhhugidqshgvtghurhhithihqdhmohguuhhlvgesvhhgvghrrdhkvghrnhgvlhdrohhr ghdprhgtphhtthhopegrmhhirhejfehilhesghhmrghilhdrtghomhdprhgtphhtthhope hrvghpnhhophesghhoohhglhgvrdgtohhmpdhrtghpthhtoheplhhinhhugidqfhhsuggv vhgvlhesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehthigthhhosehthi gthhhordhpihiiiigr X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:20:01 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 5/9] Define user structure for events and responses. Date: Tue, 4 Mar 2025 01:13:01 +0000 Message-ID: X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 The two structures are designed to be passed via read and write to the supervisor-fd. Compile time check for no holes are added to build_check_abi. The event structure will be a dynamically sized structure with possibly a NULL-terminating filename at the end. This is so that we can pass a raw filename to the supervisor for file creation requests, without having the trouble of not being able to open a fd to a file that has not been created. NOTE: despite this patch having a new uapi, I'm still very open to e.g. re-using fanotify stuff instead (if that makes sense in the end). This is just a PoC. Signed-off-by: Tingmao Wang --- include/uapi/linux/landlock.h | 107 ++++++++++++++++++++++++++++++++++ security/landlock/syscalls.c | 28 +++++++++ 2 files changed, 135 insertions(+) diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 7bc1eb4859fb..b5645fdd998d 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -318,4 +318,111 @@ struct landlock_net_port_attr { #define LANDLOCK_SCOPE_SIGNAL (1ULL << 1) /* clang-format on*/ +/** + * DOC: supervisor + * + * Supervise mode + * ~~~~~~~~~~~~~~ + * + * TODO + */ + +typedef __u16 landlock_supervise_event_type_t; +/* clang-format off */ +#define LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS 1 +#define LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS 2 +/* clang-format on */ + +struct landlock_supervise_event_hdr { + /** + * @type: Type of the event. + */ + landlock_supervise_event_type_t type; + /** + * @length: Length of the entire struct + * landlock_supervise_event including this header. + */ + __u16 length; + /** + * @cookie: Opaque identifier to be included in the response. + */ + __u32 cookie; +}; + +struct landlock_supervise_event { + struct landlock_supervise_event_hdr hdr; + __u64 access_request; + __kernel_pid_t accessor; + union { + struct { + /** + * @fd1: An open file descriptor for the file (open, + * delete, execute, link, readdir, rename, truncate), + * or the parent directory (for create operations + * targeting its child) being accessed. Must be + * closed by the reader. + * + * If this points to a parent directory, @destname + * will contain the target filename. If @destname is + * empty, this points to the target file. + */ + int fd1; + /** + * @fd2: For link or rename requests, a second file + * descriptor for the target parent directory. Must + * be closed by the reader. @destname contains the + * destination filename. This field is -1 if not + * used. + */ + int fd2; + /** + * @destname: A filename for a file creation target. + * + * If either of fd1 or fd2 points to a parent + * directory rather than the target file, this is the + * NULL-terminated name of the file that will be + * newly created. + * + * Counting the NULL terminator, this field will + * contain one or more NULL padding at the end so + * that the length of the whole struct + * landlock_supervise_event is a multiple of 8 bytes. + * + * This is a variable length member, and the length + * including the terminating NULL(s) can be derived + * from hdr.length - offsetof(struct + * landlock_supervise_event, destname). + */ + char destname[]; + }; + struct { + __u16 port; + }; + }; +}; + +/* clang-format off */ +#define LANDLOCK_SUPERVISE_DECISION_DENY 0 +#define LANDLOCK_SUPERVISE_DECISION_ALLOW 1 +/* clang-format on */ + +struct landlock_supervise_response { + /** + * @length: Size of this structure. + */ + __u16 length; + /** + * @decision: Whether to allow the request. + */ + __u8 decision; + /** + * @pad: Reserved, must be zero. + */ + __u8 _reserved; + /** + * @cookie: Cookie previously received in the request. + */ + __u32 cookie; +}; + #endif /* _UAPI_LINUX_LANDLOCK_H */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index adf7e77023b5..f1080e7de0c7 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -91,6 +91,9 @@ static void build_check_abi(void) struct landlock_path_beneath_attr path_beneath_attr; struct landlock_net_port_attr net_port_attr; size_t ruleset_size, path_beneath_size, net_port_size; + struct landlock_supervise_event *event; + struct landlock_supervise_response response; + size_t supervise_evt_size, supervise_response_size; /* * For each user space ABI structures, first checks that there is no @@ -114,6 +117,31 @@ static void build_check_abi(void) net_port_size += sizeof(net_port_attr.port); BUILD_BUG_ON(sizeof(net_port_attr) != net_port_size); BUILD_BUG_ON(sizeof(net_port_attr) != 16); + + /* Check that anything before the destname does not have holes */ + supervise_evt_size = sizeof(event->hdr.type); + supervise_evt_size += sizeof(event->hdr.length); + supervise_evt_size += sizeof(event->hdr.cookie); + BUILD_BUG_ON(offsetofend(typeof(*event), hdr) != 8); + supervise_evt_size += sizeof(event->access_request); + supervise_evt_size += sizeof(event->accessor); + supervise_evt_size += sizeof(event->fd1); + supervise_evt_size += sizeof(event->fd2); + BUILD_BUG_ON(offsetof(typeof(*event), destname) != supervise_evt_size); + BUILD_BUG_ON(offsetof(typeof(*event), destname) != 28); + + /* + * Make sure this struct does not end up with stricter + * alignment than 8 + */ + BUILD_BUG_ON(__alignof__(typeof(*event)) != 8); + + supervise_response_size = sizeof(response.length); + supervise_response_size += sizeof(response.decision); + supervise_response_size += sizeof(response._reserved); + supervise_response_size += sizeof(response.cookie); + BUILD_BUG_ON(sizeof(response) != supervise_response_size); + BUILD_BUG_ON(sizeof(response) != 8); } /* Ruleset handling */ From patchwork Tue Mar 4 01:13:02 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999747 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A5D4133D8; Tue, 4 Mar 2025 01:20:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051238; cv=none; b=k1igPSvnZzK9xbc9thmubsNjufi6GSrfXXtsAq0O82ujl6uZx0kmZ+OgfJbei06JGCtLKfJa8R76myh6a5xyVuVgSCrZqciQIr/qDd/Lp9Q9+aL3zLihiLAq1T9stTlzCJZTHd8ZscrxRidy/9ZaxB/akiQ+NOInCKA8zjtIYys= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051238; c=relaxed/simple; bh=rnprk/5LETxlTLSnMEr5WWWk1p2XJoOj2OLwf8W5610=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jE5YejLpH4lVrC0fsy3heBpfE/FdcT44K81YfXJNP4hh4JJP8pbjkmA6hbQuYCnrKTzmcZ49EFKUjnaegyz1OvImrFCWG44JV1mRkuRKnIkwM7hwGQAbDgMlsxskKpiQln3miK14QeA1gcExR4JM6vThRiSy5FigZc6Lnjzao5c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=n6ePWcnd; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=6aB70U8Q; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="n6ePWcnd"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="6aB70U8Q" Received: from phl-compute-13.internal (phl-compute-13.phl.internal [10.202.2.53]) by mailflow.stl.internal (Postfix) with ESMTP id 855681D415DB; Mon, 3 Mar 2025 20:20:35 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-13.internal (MEProxy); Mon, 03 Mar 2025 20:20:35 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1741051235; x= 1741054835; bh=+MQgzypQgiXM4OjRQeqP0MkmEnM9cTUHzJisME6efzM=; b=n 6ePWcndu8BHT63RinoGWf20O+TfoYO9eKukHH871juG8po0WWVguo/TSvdzW/FV0 atyeoJ6DnuzZ7sHYAM2D6SyekdBF30VXd/PamXh419nAJPkvsdJ17+6uWucaOC5R IjXgE2rgDlzLgwR2jtIt1K5zEfkLbU5Kjx+gsTnjwRYeLabLO1Qv9e6osFexfYTE I1xSAWOEgF0gOZFuwH5czXM2WcMOVa6prk8wPScVLDEycLx+KXJ/brcsbTPKdsjs RzZ3ZCrdU5i1TGR2hCs7e6GtokQZHvd8+MOVDo172Nl2vrne3hEKrqbJZ1wAJArk 0kq3VgM84Hu2iwS0/uJ1A== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1741051235; x=1741054835; bh=+ MQgzypQgiXM4OjRQeqP0MkmEnM9cTUHzJisME6efzM=; b=6aB70U8Q0+wXTH9wu 4AInN1MbTjI6oER/o9Ns+u2pbh7gQ0o0cfU7PHxY/0MCodOt7dimForwsiXJaq2Y hoV+qPOfD+qwz1GPGsqGjjzZZ5V6cMnyWmuQB4nySaCYx/iMUEPYBIpeZuSUXol4 gLPDrM/Li6KVg0D2w1kySn2+xNkSZFDon9p6t9ViTSfywIX6FdAzDDdyMu5gTb1S +dJXYwEgPnLECsIK9z5ZkUw1J2g+sSs0Ny47h6WQJSKtp4YSnhIJXpsUi5PAKAT6 yNpEppOVCSwu3fd0ShG1mqxR0m846e0b1ruH5/UyyWlC1MWB/u/wPwM8lFBXFdMn /uYMA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieejucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhgggfestdekredtredt tdenucfhrhhomhepvfhinhhgmhgrohcuhggrnhhguceomhesmhgrohifthhmrdhorhhgqe enucggtffrrghtthgvrhhnpeeuuddthefhhefhvdejteevvddvteefffegteetueegueel jeefueekjeetieeuleenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehmsehmrghofihtmhdrohhrghdpnhgspghrtghpthhtohepledpmhhouggv pehsmhhtphhouhhtpdhrtghpthhtohepmhhitgesughighhikhhougdrnhgvthdprhgtph htthhopehgnhhorggtkhesghhoohhglhgvrdgtohhmpdhrtghpthhtohepjhgrtghksehs uhhsvgdrtgiipdhrtghpthhtohepmhesmhgrohifthhmrdhorhhgpdhrtghpthhtoheplh hinhhugidqshgvtghurhhithihqdhmohguuhhlvgesvhhgvghrrdhkvghrnhgvlhdrohhr ghdprhgtphhtthhopegrmhhirhejfehilhesghhmrghilhdrtghomhdprhgtphhtthhope hrvghpnhhophesghhoohhglhgvrdgtohhmpdhrtghpthhtoheplhhinhhugidqfhhsuggv vhgvlhesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehthigthhhosehthi gthhhordhpihiiiigr X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:20:33 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 6/9] Creating supervisor events for filesystem operations Date: Tue, 4 Mar 2025 01:13:02 +0000 Message-ID: X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 NOTE from future me: This implementation which waits for user response while blocking inside the current security_path_* hooks is problematic due to taking exclusive inode lock on the parent directory, and while I have a proposal for a solution, outlined below, I haven't managed to include the code for that in this version of the patch. Thus for this commit in particular I'm probably more looking for suggestions on the approach rather than code review. Please see the TODO section at the end of this message before reviewing this patch. ---- This patch implements a proof-of-concept for modifying the current landlock LSM hooks to send supervisor events and wait for responses, when a supervised layer is involved. In this design, access requests which would end up being denied by other non-supervised landlock layers (or which would fail the normal inode permission check anyways - but this is currently TODO, I only thought of this afterwards) are denied straight away to avoid pointless supervisor notifications. Currently current_check_access_path only gets the path of the parent directory for create/remove operations, which is not enough for what we want to pass to the supervisor. Therefore we extend it by passing in any relevant child dentry (but see TODO below - this may not be possible with the proper implementation). This initial implementation doesn't handle links and renames, and for now these operations behave as if no supervisor is present (and thus will be denied, unless it is allowed by the layer rules). Also note that we can get spurious create requests if the program tries to O_CREAT open an existing file that exists but not in the dcache (from my understanding). Event IDs (referred to as an opaque cookie in the uapi) are currently generated with a simple `next_event_id++`. I considered using e.g. xarray but decided to not for this PoC. Suggestions welcome. (Note that we have to design our own event id even if we use an extension of fanotify, as fanotify uses a file descriptor to identify events, which is not generic enough for us) ---- TODO: When testing this I realized that doing it this way means that for the create/delete case, we end up holding an exclusive inode lock on the parent directory while waiting for supervisor to respond (see namei.c - security_path_mknod is called in may_o_create <- lookup_open which has an exclusive lock if O_CREAT is passed), which will prevent all other tasks from accessing that directory (regardless of whether or not they are under landlock). This is clearly unacceptable, but since landlock (and also this extension) doesn't actually need a dentry for the child (which is allocated after the inode lock), I think this is not unsolvable. I'm experimenting with creating a new LSM hook, something like security_pathname_mknod (suggestions welcome), which will be called after we looked up the dentry for the parent (to prevent racing symlinks TOCTOU), but before we take the lock for it. Such a hook can still take as argument the parent dentry, plus name of the child (instead of a struct path for it). Suggestions for alternative approaches are definitely welcome! Signed-off-by: Tingmao Wang --- security/landlock/fs.c | 134 ++++++++++++++++++++++++++++++++-- security/landlock/supervise.c | 122 +++++++++++++++++++++++++++++++ security/landlock/supervise.h | 106 ++++++++++++++++++++++++++- 3 files changed, 354 insertions(+), 8 deletions(-) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 71b9dc331aae..5c147edb6fff 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -44,6 +44,7 @@ #include "object.h" #include "ruleset.h" #include "setup.h" +#include "supervise.h" /* Underlying object management */ @@ -924,10 +925,13 @@ static bool is_access_to_paths_allowed( } static int current_check_access_path(const struct path *const path, + struct dentry *const child, access_mask_t access_request) { const struct landlock_ruleset *const dom = get_current_fs_domain(); layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; + bool is_remove = !!(access_request & (LANDLOCK_ACCESS_FS_REMOVE_FILE | + LANDLOCK_ACCESS_FS_REMOVE_DIR)); if (!dom) return 0; @@ -938,6 +942,29 @@ static int current_check_access_path(const struct path *const path, NULL, 0, NULL, NULL)) return 0; + if (landlock_has_supervisors(dom)) { + layer_mask_t pending_ask_supervise_layers = + landlock_layer_masks_to_denied_layers( + access_request, layer_masks, + sizeof(layer_masks), dom->num_layers); + + WARN_ON_ONCE(!pending_ask_supervise_layers); + + struct path child_path = *path; + if (child) { + child_path.dentry = child; + } + + bool supervisor_allowed = landlock_ask_supervised_layers( + dom, pending_ask_supervise_layers, + LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS, access_request, + &child_path, NULL, child && !is_remove, false, 0); + + if (supervisor_allowed) { + return 0; + } + } + return -EACCES; } @@ -1092,6 +1119,8 @@ static bool collect_domain_accesses( * - 0 if access is allowed; * - -EXDEV if @old_dentry would inherit new access rights from @new_dir; * - -EACCES if file removal or creation is denied. + * + * TODO: implement interation wiht supervisors. */ static int current_check_refer_path(struct dentry *const old_dentry, const struct path *const new_dir, @@ -1415,38 +1444,43 @@ static int hook_path_rename(const struct path *const old_dir, static int hook_path_mkdir(const struct path *const dir, struct dentry *const dentry, const umode_t mode) { - return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR); + return current_check_access_path(dir, dentry, + LANDLOCK_ACCESS_FS_MAKE_DIR); } static int hook_path_mknod(const struct path *const dir, struct dentry *const dentry, const umode_t mode, const unsigned int dev) { - return current_check_access_path(dir, get_mode_access(mode)); + return current_check_access_path(dir, dentry, get_mode_access(mode)); } static int hook_path_symlink(const struct path *const dir, struct dentry *const dentry, const char *const old_name) { - return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM); + return current_check_access_path(dir, dentry, + LANDLOCK_ACCESS_FS_MAKE_SYM); } static int hook_path_unlink(const struct path *const dir, struct dentry *const dentry) { - return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE); + return current_check_access_path(dir, dentry, + LANDLOCK_ACCESS_FS_REMOVE_FILE); } static int hook_path_rmdir(const struct path *const dir, struct dentry *const dentry) { - return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); + return current_check_access_path(dir, dentry, + LANDLOCK_ACCESS_FS_REMOVE_DIR); } static int hook_path_truncate(const struct path *const path) { - return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE); + return current_check_access_path(path, NULL, + LANDLOCK_ACCESS_FS_TRUNCATE); } /* File hooks */ @@ -1562,9 +1596,81 @@ static int hook_file_open(struct file *const file) if ((open_access_request & allowed_access) == open_access_request) return 0; + if (landlock_has_supervisors(dom)) { + layer_mask_t pending_ask_supervise_layers = + landlock_layer_masks_to_denied_layers( + open_access_request, layer_masks, + sizeof(layer_masks), dom->num_layers); + + WARN_ON_ONCE(!pending_ask_supervise_layers); + + /* + * We don't need to ask the supervisor for optional + * access right now - we can ask later. + */ + + bool supervisor_allowed = landlock_ask_supervised_layers( + dom, pending_ask_supervise_layers, + LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS, + open_access_request, &file->f_path, NULL, false, false, + 0); + + if (supervisor_allowed) { + landlock_file(file)->allowed_access = + open_access_request; + return 0; + } + } + return -EACCES; } +/* + * For any "optional" permissions (truncate and ioctl) which was + * not allowed at time a file was opened, we want to check with + * any supervised layers if they actually allow it at the time + * the user tries to do such an operation on the opened fd. We + * can check for access on the path (using the opener's domain) + * as the opener can never re-gain permissions under landlock. + */ +static bool check_opened_file_access_supervisor(struct file *const file, + access_mask_t access_request) +{ + const struct landlock_ruleset *dom = landlock_get_applicable_domain( + landlock_cred(file->f_cred)->domain, any_fs); + + if (landlock_has_supervisors(dom)) { + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; + bool allowed = is_access_to_paths_allowed( + dom, &file->f_path, + landlock_init_layer_masks(dom, access_request, + &layer_masks, + LANDLOCK_KEY_INODE), + &layer_masks, NULL, 0, NULL, NULL); + if (allowed) { + WARN_ONCE( + 1, + "Access was previously not allowed, now it's allowed in the same domain. Landlock bug?"); + return false; + } + + layer_mask_t pending_ask_supervise_layers = + landlock_layer_masks_to_denied_layers( + access_request, layer_masks, + sizeof(layer_masks), dom->num_layers); + WARN_ON_ONCE(!pending_ask_supervise_layers); + + bool supervisor_allowed = landlock_ask_supervised_layers( + dom, pending_ask_supervise_layers, + LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS, access_request, + &file->f_path, NULL, false, false, 0); + + return supervisor_allowed; + } + + return false; +} + static int hook_file_truncate(struct file *const file) { /* @@ -1579,6 +1685,12 @@ static int hook_file_truncate(struct file *const file) */ if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE) return 0; + + if (check_opened_file_access_supervisor(file, + LANDLOCK_ACCESS_FS_TRUNCATE)) { + return 0; + } + return -EACCES; } @@ -1602,6 +1714,11 @@ static int hook_file_ioctl(struct file *file, unsigned int cmd, if (is_masked_device_ioctl(cmd)) return 0; + if (check_opened_file_access_supervisor(file, + LANDLOCK_ACCESS_FS_IOCTL_DEV)) { + return 0; + } + return -EACCES; } @@ -1625,6 +1742,11 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd, if (is_masked_device_ioctl_compat(cmd)) return 0; + if (check_opened_file_access_supervisor(file, + LANDLOCK_ACCESS_FS_IOCTL_DEV)) { + return 0; + } + return -EACCES; } diff --git a/security/landlock/supervise.c b/security/landlock/supervise.c index a3bb6928f453..3f31a89c4c96 100644 --- a/security/landlock/supervise.c +++ b/security/landlock/supervise.c @@ -12,6 +12,12 @@ #include "supervise.h" +#ifdef pr_fmt +#undef pr_fmt +#endif + +#define pr_fmt(fmt) "landlock-supervise: " fmt + struct landlock_supervisor *landlock_create_supervisor(void) { struct landlock_supervisor *supervisor; @@ -70,3 +76,119 @@ void landlock_put_supervisor(struct landlock_supervisor *const supervisor) kfree(supervisor); } } + +/** + * landlock_ask_supervised_layers - check if all denied layers + * are supervised, and if yes, ask all of them for permission. + * + * Return whether access should be allowed. If denied_layers + * contains any non-supervised layer, will return false without + * making any supervisor event. + * + * Caller owns any paths passed in, we might get refs. + */ +bool landlock_ask_supervised_layers( + const struct landlock_ruleset *const domain, + const layer_mask_t denied_layers, + const landlock_supervise_event_type_t request_type, + const access_mask_t access_request, const struct path *const path1, + const struct path *const path2, const bool path1_new, + const bool path2_new, const __u16 port) +{ + size_t layer_level; + unsigned long denied_layers_ = denied_layers; + + if (WARN_ON_ONCE(!denied_layers)) { + return true; + } + + for_each_set_bit(layer_level, &denied_layers_, domain->num_layers) { + if (!domain->layer_stack[layer_level].supervisor) { + return false; + } + } + + /* + * All denied layers are supervisor layers, so we just ask + * them in turn. There's good argument for either order (top + * -> bottom, or the other way), so we just do the easiest + * thing here. + */ + + for_each_set_bit(layer_level, &denied_layers_, domain->num_layers) { + struct landlock_supervisor *const supervisor = + domain->layer_stack[layer_level].supervisor; + + /* + * supervisor will stay valid here because we're blocking + * this thread which references the layer, which in terms + * references the supervisor. + */ + + /* TODO: memchg supervisor owner then allocate with account */ + struct landlock_supervise_event_kernel *event __free( + landlock_put_supervise_event) = + kzalloc(sizeof(*event), GFP_KERNEL_ACCOUNT); + + int rc; + + if (!event) { + pr_alert( + "failed to allocate memory for supervisor event\n"); + return false; + } + + refcount_set(&event->usage, 1); + event->state = LANDLOCK_SUPERVISE_EVENT_NEW; + + event->type = request_type; + event->access_request = access_request; + event->accessor = get_pid(task_pid(current)); + switch (request_type) { + case LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS: + if (path1) { + path_get(path1); + event->target_1 = *path1; + event->target_1_is_new = path1_new; + } + if (path2) { + path_get(path2); + event->target_2 = *path2; + event->target_2_is_new = path2_new; + } + break; + case LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS: + event->port = port; + break; + } + + if (WARN_ON(!supervisor)) { + /* + * We checked all denied layers are supervised + * earlier... + */ + return false; + } + + spin_lock(&supervisor->lock); + event->event_id = supervisor->next_event_id++; + landlock_get_supervise_event(event); + list_add_tail(&event->node, &supervisor->event_queue); + spin_unlock(&supervisor->lock); + wake_up(&supervisor->poll_event_wq); + + rc = wait_var_event_killable( + event, LANDLOCK_SUPERVISE_EVENT_HANDLED(event)); + if (rc) { + /* Task died, doesn't matter what we say */ + return false; + } + if (event->state != LANDLOCK_SUPERVISE_EVENT_ALLOWED) { + return false; + } + + /* event has __free */ + } + + return true; +} diff --git a/security/landlock/supervise.h b/security/landlock/supervise.h index febe26a11578..10fc274fabb7 100644 --- a/security/landlock/supervise.h +++ b/security/landlock/supervise.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "access.h" #include "ruleset.h" @@ -46,9 +47,56 @@ struct landlock_supervise_event_kernel { refcount_t usage; enum landlock_supervise_event_state state; - /* more fields to come */ + /* Cookie as presented to user-space */ + u32 event_id; + + landlock_supervise_event_type_t type; + access_mask_t access_request; + struct pid *accessor; + union { + struct { + /** + * @target_1: The first (and may be the only, for + * most requests) target path. To expose as much + * useful information to the supervisor as possible, + * for file creation and deletion, this points to the + * actual path being created (or deleted), rather + * than the parent directory. Note that for the + * create case, this means that the dentry will be + * negative (unless we end up in some horrible race). + * In the create case, target_1_is_new is set, so + * that we know to pass the parent as the fd to the + * user-space supervisor, and fill destname with the + * name of the file. + * + * For refer (link and rename), this points to the + * source (or simply the first argument in case of + * exchange) being linked. It will necessarily have + * to be an existing file (even though the dentry may + * turn negative). + */ + struct path target_1; + /** + * @target_2: The destination path for link and + * rename (or simply the second argument in case of + * exchange). target_2_is_new will be set unless this + * is an exchange. + */ + struct path target_2; + + u8 target_1_is_new : 1; + u8 target_2_is_new : 1; + }; + struct { + __u16 port; + }; + }; }; +#define LANDLOCK_SUPERVISE_EVENT_HANDLED(event) \ + ((event)->state == LANDLOCK_SUPERVISE_EVENT_ALLOWED || \ + (event)->state == LANDLOCK_SUPERVISE_EVENT_DENIED) + struct landlock_supervisor *landlock_create_supervisor(void); void landlock_get_supervisor(struct landlock_supervisor *const supervisor); void landlock_put_supervisor(struct landlock_supervisor *const supervisor); @@ -62,8 +110,62 @@ static inline void landlock_get_supervise_event( static inline void landlock_put_supervise_event( struct landlock_supervise_event_kernel *const event) { - if (refcount_dec_and_test(&event->usage)) + if (refcount_dec_and_test(&event->usage)) { + switch (event->type) { + case LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS: + if (event->target_1.dentry) + path_put(&event->target_1); + if (event->target_2.dentry) + path_put(&event->target_2); + break; + case LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS: + break; + } + put_pid(event->accessor); kfree(event); + } +} + +DEFINE_FREE(landlock_put_supervise_event, + struct landlock_supervise_event_kernel *, + if (_T) landlock_put_supervise_event(_T)) + +static inline bool +landlock_has_supervisors(const struct landlock_ruleset *const domain) +{ + size_t layer_level; + for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { + if (domain->layer_stack[layer_level].supervisor) + return true; + } + return false; } +static inline layer_mask_t landlock_layer_masks_to_denied_layers( + const access_mask_t access_request, const layer_mask_t layer_masks[], + const size_t masks_array_size, const int num_layers) +{ + unsigned long access_req = access_request; + layer_mask_t denied_layers = 0; + size_t layer_level; + unsigned long access_bit; + + for (layer_level = 0; layer_level < num_layers; layer_level++) { + for_each_set_bit(access_bit, &access_req, masks_array_size) { + if (layer_masks[access_bit] & BIT_ULL(layer_level)) + denied_layers |= BIT_ULL(layer_level); + } + } + + return denied_layers; +} + +bool landlock_ask_supervised_layers( + const struct landlock_ruleset *const domain, + const layer_mask_t denied_layers, + const landlock_supervise_event_type_t request_type, + const access_mask_t access_request, const struct path *const path1, + const struct path *const path2, const bool path1_new, + const bool path2_new, const __u16 port); + #endif /* _SECURITY_LANDLOCK_SUPERVISE_H */ From patchwork Tue Mar 4 01:13:03 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999755 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BE3BD22092; Tue, 4 Mar 2025 01:21:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051263; cv=none; b=P3U7KBUId4PEZxvhfKdSBoyQ7UVKVu3URbjc90487JqOAlF2jLnG7HIrsPfffF+8IRWnAxflSm+tfr8ZCcj9WBfhemfcaZcbuALNvhX8M1LkmOOpHKn0H55WLhQJ2b7T/A51lhqG2HmSJdAP6c2MSuX8L5JoYQuLI2ciW0RmUvY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051263; c=relaxed/simple; bh=Hsw+AMk4pRtKuraWYKtIjzWeF8iSeu5Jokn2tnsObuA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=I4yzUawb/tQKycLxhbl+egnj2rJ8TLlooyaJAIiUVVaGE3UyjxnO9L90dODg07qpVKJpHN4wk/BxRiA3cZOWRZ06mPKqKyiOsj/Qgdybd3Ae1lQWnQmzgVlVyhNqAw8k0ng8LoV9snPIy4YOlmgBBoJrOdMNTI1WuNxbDRanuuA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=aFPNH2In; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=JVyXtXIN; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="aFPNH2In"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="JVyXtXIN" Received: from phl-compute-08.internal (phl-compute-08.phl.internal [10.202.2.48]) by mailflow.stl.internal (Postfix) with ESMTP id 8816B1D415DD; Mon, 3 Mar 2025 20:21:00 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-08.internal (MEProxy); Mon, 03 Mar 2025 20:21:00 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1741051260; x= 1741054860; bh=Indt7uouUdIV1MUQRSDcszN4dc63JyY5LYMIguCfl5g=; b=a FPNH2InAF25Gq/+LCU+rudz8ednodDhHmfEzy2tDs/q1kRuHGyZu36AaqNKEEA6E EpSAmbNUGy/gqVIL/8eP0PzG8g1f9S0anfRjjxcJ/HvUYPx2aOOW0vwUikq+QGdW KWO3nNihiGTine40aRJcsI0uYqncg6Y8ZorcoaTbKY9qTnGkotoZHF5ziF8JJaPY MKv1Iy9wNB1vt8BOA4YFYGxKJ2sTICaqcTuW7ESIywNRNYYLab8iLvq2zDJlY+EC 2RxKmvEvaFyoToBsivSINRJxmSOA60Q9iBM2ngFH/Hp/+X4JglgbgDhVfl5dnRLQ qw92ZBaTXEqWfq9hcwoew== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1741051260; x=1741054860; bh=I ndt7uouUdIV1MUQRSDcszN4dc63JyY5LYMIguCfl5g=; b=JVyXtXINeBUy0Z7eF S9PwrgiNR5JI6iKO9ugMdRGcXPBYVwnOdBGLe3tAkZdkjk2Y2Uw6e9qWu3HIw3nr j694IINK2RG3FjU8beEEnTe34ySfAI3eUlfX1xEiWpJhtw5oXDQq9i8P6kIpgXMF e3yHwAOyBLsKs0wfLWRxonWYpDLI8rmOAssFjhYjCRwKFOH2laP3QX0kpZfSpggW V1B3U+gmD5IPF7lrSMKbgm1OLizKGycUTHgPSuzoKZnYlli10FrA7Y51YDzefT4d r9Ok5apqA33mZFqxHE6BFAKmWfitY1/LxA2gQ9Inkbz+cK2KPHssbxBWbtKytbQA tdXbw== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieekucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhgggfestdekredtredt tdenucfhrhhomhepvfhinhhgmhgrohcuhggrnhhguceomhesmhgrohifthhmrdhorhhgqe enucggtffrrghtthgvrhhnpeeuuddthefhhefhvdejteevvddvteefffegteetueegueel jeefueekjeetieeuleenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehmsehmrghofihtmhdrohhrghdpnhgspghrtghpthhtohepledpmhhouggv pehsmhhtphhouhhtpdhrtghpthhtohepmhhitgesughighhikhhougdrnhgvthdprhgtph htthhopehgnhhorggtkhesghhoohhglhgvrdgtohhmpdhrtghpthhtohepjhgrtghksehs uhhsvgdrtgiipdhrtghpthhtohepmhesmhgrohifthhmrdhorhhgpdhrtghpthhtoheplh hinhhugidqshgvtghurhhithihqdhmohguuhhlvgesvhhgvghrrdhkvghrnhgvlhdrohhr ghdprhgtphhtthhopegrmhhirhejfehilhesghhmrghilhdrtghomhdprhgtphhtthhope hrvghpnhhophesghhoohhglhgvrdgtohhmpdhrtghpthhtoheplhhinhhugidqfhhsuggv vhgvlhesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehthigthhhosehthi gthhhordhpihiiiigr X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:20:58 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 7/9] Implement fdinfo for ruleset and supervisor fd Date: Tue, 4 Mar 2025 01:13:03 +0000 Message-ID: X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Purely for ease of debugging. Shows whether a ruleset is in supervisor mode, and for the supervisor fd, any events. Signed-off-by: Tingmao Wang --- include/uapi/linux/landlock.h | 2 + security/landlock/syscalls.c | 146 ++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index b5645fdd998d..2b2a21c1b6cf 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -270,6 +270,7 @@ struct landlock_net_port_attr { #define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14) #define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15) /* clang-format on */ +/* Add extra entries to access_request_to_string too */ /** * DOC: net_access @@ -292,6 +293,7 @@ struct landlock_net_port_attr { #define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0) #define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1) /* clang-format on */ +/* Add extra entries to access_request_to_string too */ /** * DOC: scope diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index f1080e7de0c7..3018e3663173 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -170,6 +170,17 @@ static ssize_t fop_dummy_write(struct file *const filp, return -EINVAL; } +static void fop_ruleset_fdinfo(struct seq_file *const m, struct file *const f) +{ + struct landlock_ruleset *const ruleset = f->private_data; + + seq_printf(m, "num_rules: %d\n", ruleset->num_rules); + if (ruleset->layer_stack[0].supervisor) + seq_puts(m, "supervisor: yes\n"); + else + seq_puts(m, "supervisor: no\n"); +} + /* * A ruleset file descriptor enables to build a ruleset by adding (i.e. * writing) rule after rule, without relying on the task's context. This @@ -180,6 +191,7 @@ static const struct file_operations ruleset_fops = { .release = fop_ruleset_release, .read = fop_dummy_read, .write = fop_dummy_write, + .show_fdinfo = fop_ruleset_fdinfo, }; static int fop_supervisor_release(struct inode *const inode, @@ -191,11 +203,145 @@ static int fop_supervisor_release(struct inode *const inode, return 0; } +static const char * +event_state_to_string(enum landlock_supervise_event_state state) +{ + switch (state) { + case LANDLOCK_SUPERVISE_EVENT_NEW: + return "new"; + case LANDLOCK_SUPERVISE_EVENT_NOTIFIED: + return "notified"; + case LANDLOCK_SUPERVISE_EVENT_ALLOWED: + return "allowed"; + case LANDLOCK_SUPERVISE_EVENT_DENIED: + return "denied"; + default: + WARN_ONCE(1, "unknown event state\n"); + return "unknown"; + } +} + +static void +access_request_to_string(const landlock_supervise_event_type_t access_type, + const access_mask_t access_request, struct seq_file *m) +{ + switch (access_type) { + case LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS: + if (access_request & LANDLOCK_ACCESS_FS_EXECUTE) + seq_puts(m, "FS_EXECUTE "); + if (access_request & LANDLOCK_ACCESS_FS_WRITE_FILE) + seq_puts(m, "FS_WRITE_FILE "); + if (access_request & LANDLOCK_ACCESS_FS_READ_FILE) + seq_puts(m, "FS_READ_FILE "); + if (access_request & LANDLOCK_ACCESS_FS_READ_DIR) + seq_puts(m, "FS_READ_DIR "); + if (access_request & LANDLOCK_ACCESS_FS_REMOVE_DIR) + seq_puts(m, "FS_REMOVE_DIR "); + if (access_request & LANDLOCK_ACCESS_FS_REMOVE_FILE) + seq_puts(m, "FS_REMOVE_FILE "); + if (access_request & LANDLOCK_ACCESS_FS_MAKE_CHAR) + seq_puts(m, "FS_MAKE_CHAR "); + if (access_request & LANDLOCK_ACCESS_FS_MAKE_DIR) + seq_puts(m, "FS_MAKE_DIR "); + if (access_request & LANDLOCK_ACCESS_FS_MAKE_REG) + seq_puts(m, "FS_MAKE_REG "); + if (access_request & LANDLOCK_ACCESS_FS_MAKE_SOCK) + seq_puts(m, "FS_MAKE_SOCK "); + if (access_request & LANDLOCK_ACCESS_FS_MAKE_FIFO) + seq_puts(m, "FS_MAKE_FIFO "); + if (access_request & LANDLOCK_ACCESS_FS_MAKE_BLOCK) + seq_puts(m, "FS_MAKE_BLOCK "); + if (access_request & LANDLOCK_ACCESS_FS_MAKE_SYM) + seq_puts(m, "FS_MAKE_SYM "); + if (access_request & LANDLOCK_ACCESS_FS_REFER) + seq_puts(m, "FS_REFER "); + if (access_request & LANDLOCK_ACCESS_FS_TRUNCATE) + seq_puts(m, "FS_TRUNCATE "); + if (access_request & LANDLOCK_ACCESS_FS_IOCTL_DEV) + seq_puts(m, "FS_IOCTL_DEV "); + break; + case LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS: + if (access_request & LANDLOCK_ACCESS_NET_BIND_TCP) + seq_puts(m, "NET_BIND_TCP "); + if (access_request & LANDLOCK_ACCESS_NET_CONNECT_TCP) + seq_puts(m, "NET_CONNECT_TCP "); + break; + } +} + +static void fop_supervisor_fdinfo(struct seq_file *m, struct file *f) +{ + struct landlock_supervisor *const supervisor = f->private_data; + struct landlock_supervise_event_kernel *event; + + spin_lock(&supervisor->lock); + + size_t cnt = list_count_nodes(&supervisor->event_queue); + seq_printf(m, "num_events: %zu\n", cnt); + list_for_each_entry(event, &supervisor->event_queue, node) { + struct task_struct *task = + get_pid_task(event->accessor, PIDTYPE_PID); + + seq_puts(m, "event:\n"); + if (task) { + seq_printf(m, "\taccessor: %s[%d]\n", task->comm, + task->pid); + put_task_struct(task); + } else { + seq_puts(m, "\taccessor: defunct\n"); + } + + if (event->type == LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS) { + seq_puts(m, "\taccess: filesystem\n"); + seq_printf(m, "\taccess_request: %llu ", + (unsigned long long)event->access_request); + access_request_to_string(event->type, + event->access_request, m); + seq_puts(m, "\n"); + if (event->target_1.dentry) { + /* + * ok to access since event owns a ref to the + * path, and we have event list spin lock. + */ + if (event->target_1_is_new) { + seq_puts(m, "\ttarget_1 (new): "); + } else { + seq_puts(m, "\ttarget_1: "); + } + seq_path(m, &event->target_1, ""); + seq_puts(m, "\n"); + } + if (event->target_2.dentry) { + if (event->target_2_is_new) { + seq_puts(m, "\ttarget_2 (new): "); + } else { + seq_puts(m, "\ttarget_2: "); + } + seq_path(m, &event->target_2, ""); + seq_puts(m, "\n"); + } + } else if (event->type == + LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS) { + seq_puts(m, "\taccess: network\n"); + seq_printf(m, "\tport: %u\n", + (unsigned int)event->port); + } else { + WARN(1, "unknown event key type\n"); + } + + seq_printf(m, "\tstate: %s\n", + event_state_to_string(event->state)); + } + + spin_unlock(&supervisor->lock); +} + static const struct file_operations supervisor_fops = { .release = fop_supervisor_release, /* TODO: read, write, poll, dup */ .read = fop_dummy_read, .write = fop_dummy_write, + .show_fdinfo = fop_supervisor_fdinfo, }; static int From patchwork Tue Mar 4 01:13:04 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999756 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B218338DC8; Tue, 4 Mar 2025 01:21:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051301; cv=none; b=AUnFiQDO7FwcXjlkd1Eoms/ZOHRZY1UT+jLMRUGdkto6B7Mm1LisemFA2ywV6NvK45/Bk0Zun62ID4RzIkkyoZ9IaiRfPlImE3INJwWFIurdZQB+zc4ZVavT6rPRElbmtkSQqwK8KyVFrSkvFg90URwEM8t7RBg1Yh6PHSW03/A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051301; c=relaxed/simple; bh=1Mn2+uQQ2KRXMmbUb9a3CWJIPCf1eEI2cF0Pvk/Y3eY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gkFJF5WEarnz+2O8XSM4k/d0r8XEJIxkfuDbqtCkkVwhlPz0qEdx4TKPRmCmFpAP3Zn4PjC6rXGfifNpIvZTrq8kgLa988kYwTT568vymqVHyVHytveoJoI8oTDDRK/hWJ27OQm1MVoABzAcDl04E36VOAc1s5lSgHD7w1gzcy0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=IXnkfQQM; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=pFhLA5Cb; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="IXnkfQQM"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="pFhLA5Cb" Received: from phl-compute-12.internal (phl-compute-12.phl.internal [10.202.2.52]) by mailflow.stl.internal (Postfix) with ESMTP id 9CEB11D4151B; Mon, 3 Mar 2025 20:21:38 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-12.internal (MEProxy); Mon, 03 Mar 2025 20:21:38 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1741051298; x= 1741054898; bh=aspxL3lmWFQP1jsGikwXAoHhhRl7ll7aEVe5a3LCQYo=; b=I XnkfQQMq2hZaBkM/zmMiVsA+2oGcvRXuDoCmjOgh7hQdq4mO+53DgjINU1JgOpWU DboPg4AI7OP+1tdr7BqmzUyzktR8uTiTjgqXfJxrqcnIiGEjipLNn4cj6K3Jb7dN 4h0e75f+wc/uxhH5nFZgSrSFKYdE1eOAVL2nX3sw3alfSil6+HvLqyC0e/yMWxwI RaNKwkoSmKCdUj+kG6GIU8+x8bjCCwlf5+f4xaffDgmu6WiDexQRqtHQYn86+S5k uB8wWI3c5D6VNc1fkesQBuCS7LTq1acYYzfDM8Sb3caVxbTNfdFve+fCGMk9GlU9 evRfHHl2PZg5nPIMFXnrA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1741051298; x=1741054898; bh=a spxL3lmWFQP1jsGikwXAoHhhRl7ll7aEVe5a3LCQYo=; b=pFhLA5CbGUe6BMznF 5818kzTPuu0/yXvqYKmSApQk2fq32WlQUl/GBEzcebMUWW5ajqi65knYfTK8PZGw mUmABJ6jZ/mWu07xmqbS482iEzTbN+DluTEdXnGNh3DFqNi9HOuXTmld1+7jo6vp rTwfVwy2JnGh+6qk81Y+vIFqT3jHO4KnSWqTq4iD5KCk0GHzREiT5iLe1ocEecp2 QZoFgJMoztG/BYnLp19X/bYH0HkWIbUq5WDpvguTTjg4sdQvMfm1JWStGBNCSLAR 3LV2bdz4w/XeNG+BuhwT1Vol04dcumHjaeY0/mQl3uGpBsmD8aNFb9M4rHbprWnD H1iug== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieejucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhgggfestdekredtredt tdenucfhrhhomhepvfhinhhgmhgrohcuhggrnhhguceomhesmhgrohifthhmrdhorhhgqe enucggtffrrghtthgvrhhnpeeuuddthefhhefhvdejteevvddvteefffegteetueegueel jeefueekjeetieeuleenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehmsehmrghofihtmhdrohhrghdpnhgspghrtghpthhtohepledpmhhouggv pehsmhhtphhouhhtpdhrtghpthhtohepmhhitgesughighhikhhougdrnhgvthdprhgtph htthhopehgnhhorggtkhesghhoohhglhgvrdgtohhmpdhrtghpthhtohepjhgrtghksehs uhhsvgdrtgiipdhrtghpthhtohepmhesmhgrohifthhmrdhorhhgpdhrtghpthhtoheplh hinhhugidqshgvtghurhhithihqdhmohguuhhlvgesvhhgvghrrdhkvghrnhgvlhdrohhr ghdprhgtphhtthhopegrmhhirhejfehilhesghhmrghilhdrtghomhdprhgtphhtthhope hrvghpnhhophesghhoohhglhgvrdgtohhmpdhrtghpthhtoheplhhinhhugidqfhhsuggv vhgvlhesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehthigthhhosehthi gthhhordhpihiiiigr X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:21:36 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 8/9] Implement fops for supervisor-fd Date: Tue, 4 Mar 2025 01:13:04 +0000 Message-ID: X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This patch exposes the events to user-space via read and receives response back via writes to the fd. We will set aside the problem of how to handle situations where the supervisor don't actually have the permission to open a fd for the path for now (and just deny the event on any error), but note that landlock does not restrict opening of O_PATH fds, and so at least a supervisor supervising itself is not completely out of the question (but the usefulness of this is perhaps questionable). NOTE: despite this patch having a new uapi, I'm still very open to e.g. re-using fanotify stuff instead (if that makes sense in the end). This is just a PoC. Signed-off-by: Tingmao Wang --- security/landlock/syscalls.c | 349 ++++++++++++++++++++++++++++++++++- 1 file changed, 346 insertions(+), 3 deletions(-) diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 3018e3663173..7d191c946ecc 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -203,6 +203,348 @@ static int fop_supervisor_release(struct inode *const inode, return 0; } +/** + * Lifetime of return value is tied to p. + */ +static struct path p_parent(struct path p) +{ + struct path parent_path = { .mnt = p.mnt, + .dentry = p.dentry->d_parent }; + return parent_path; +} + +/** + * Open an O_PATH fd of a target file for passing to the + * supervisor. + */ +static int supervise_fs_fd_open_install(struct path *path) +{ + int fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) { + pr_warn("get_unused_fd_flags: %pe\n", ERR_PTR(fd)); + return fd; + } + struct file *f = dentry_open(path, O_PATH | O_CLOEXEC, current_cred()); + if (IS_ERR(f)) { + pr_warn("Failed to open fd in supervisor: %ld\n", PTR_ERR(f)); + put_unused_fd(fd); + return PTR_ERR(f); + } + fd_install(fd, f); + return fd; +} + +static ssize_t fop_supervisor_read(struct file *const filp, + char __user *const buf, const size_t size, + loff_t *const ppos) +{ + struct landlock_supervisor *supervisor = filp->private_data; + struct landlock_supervise_event_kernel *event = NULL; + bool found = false; + struct landlock_supervise_event *user_event = NULL; + size_t destname_size = 0, event_size = 0; + const size_t dest_offset = + offsetof(struct landlock_supervise_event, destname); + const char *destname = NULL; /* Lifetime tied to event */ + int fd1 = -1, fd2 = -1, ret = 0; + bool nonblock = filp->f_flags & O_NONBLOCK; + struct path parent_path; + + if (WARN_ON(!supervisor)) + return -ENODEV; + + if (size < sizeof(struct landlock_supervise_event)) + return -EINVAL; + +retry: + spin_lock(&supervisor->lock); + + /* + * Find the first new event (but really, all events in this + * list should be new) + */ + list_for_each_entry(event, &supervisor->event_queue, node) { + if (event->state == LANDLOCK_SUPERVISE_EVENT_NEW) { + found = true; + break; + } + } + + if (!found) { + spin_unlock(&supervisor->lock); + if (nonblock) { + return -EAGAIN; + } + + /* + * Wait for events to be added to the queue. + * Not sure if we can call list_empty() without the lock + * here, hence true. + */ + ret = wait_event_interruptible(supervisor->poll_event_wq, true); + if (ret) + return ret; + + goto retry; + } + + /* + * We take the event out of the list and let other readers + * carry on. We take over the event's ownership from the + * list (hence no get/put). + */ + list_del(&event->node); + spin_unlock(&supervisor->lock); + + if (event->type == LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS) { + struct dentry *dest_dentry; + + if (WARN_ON(event->target_1_is_new && event->target_2_is_new)) { + ret = -EAGAIN; + goto fail_deny; + } + + /* + * Get destname out here so that we know the event's size. + * We separate the lifetime of destname away from the + * kernel event so we can move the copy outside of lock. + */ + if (event->target_1.dentry && event->target_1_is_new) { + dest_dentry = event->target_1.dentry; + destname = (char *)dest_dentry->d_name.name; + destname_size = dest_dentry->d_name.len + 1; + } else if (event->target_2.dentry && event->target_2_is_new) { + dest_dentry = event->target_2.dentry; + destname = (char *)dest_dentry->d_name.name; + destname_size = dest_dentry->d_name.len + 1; + } + } + + event_size = ALIGN(dest_offset + destname_size, + __alignof__(typeof(*user_event))); + + if (event_size > size) { + ret = -EINVAL; + goto fail_readd_event; + } + + /* We will copy the destname directly to user buffer */ + user_event = + kzalloc(sizeof(struct landlock_supervise_event), GFP_KERNEL); + if (!user_event) + return -ENOMEM; + + user_event->hdr.type = event->type; + user_event->hdr.length = event_size; + user_event->hdr.cookie = event->event_id; + user_event->access_request = event->access_request; + user_event->accessor = pid_vnr(event->accessor); + + /* Set up the appropriate file descriptors based on the type */ + if (event->type == LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS) { + if (event->target_1.dentry) { + if (event->target_1_is_new) { + parent_path = p_parent(event->target_1); + fd1 = supervise_fs_fd_open_install( + &parent_path); + if (fd1 < 0) { + ret = fd1; + goto fail_deny_or_readd; + } + } else { + fd1 = supervise_fs_fd_open_install( + &event->target_1); + if (fd1 < 0) { + ret = fd1; + goto fail_deny_or_readd; + } + } + } + + if (event->target_2.dentry) { + if (event->target_2_is_new) { + parent_path = p_parent(event->target_2); + fd2 = supervise_fs_fd_open_install( + &parent_path); + if (fd2 < 0) { + ret = fd2; + goto fail_deny_or_readd; + } + } else { + fd2 = supervise_fs_fd_open_install( + &event->target_2); + if (fd2 < 0) { + ret = fd2; + goto fail_deny_or_readd; + } + } + } + } else if (event->type == LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS) { + user_event->port = event->port; + } + + user_event->fd1 = fd1; + user_event->fd2 = fd2; + + /* Non-variable-sized part */ + if (copy_to_user(buf, user_event, dest_offset)) { + ret = -EFAULT; + goto fail_readd_event; + } + + /* destname */ + if (destname && destname_size > 0) { + if (copy_to_user(buf + dest_offset, destname, destname_size)) { + ret = -EFAULT; + goto fail_readd_event; + } + } + + /* Zero out any padding bytes */ + if (event_size > dest_offset + destname_size) { + size_t padding_len = event_size - dest_offset - destname_size; + if (clear_user(buf + dest_offset + destname_size, + padding_len)) { + ret = -EFAULT; + goto fail_readd_event; + } + } + + ret = event_size; + event->state = LANDLOCK_SUPERVISE_EVENT_NOTIFIED; + /* No decision yet, don't wake up! */ + spin_lock(&supervisor->lock); + list_add(&event->node, &supervisor->notified_events); + event = NULL; + spin_unlock(&supervisor->lock); + goto free; + +fail_deny_or_readd: + if (ret == -EINTR) + goto fail_readd_event; + else + goto fail_deny; + +fail_readd_event: + WARN_ON(event->state != LANDLOCK_SUPERVISE_EVENT_NEW); + spin_lock(&supervisor->lock); + list_add(&event->node, &supervisor->event_queue); + event = NULL; + spin_unlock(&supervisor->lock); + goto free; + +fail_deny: + event->state = LANDLOCK_SUPERVISE_EVENT_DENIED; + wake_up_var(event); + landlock_put_supervise_event(event); + event = NULL; + goto free; + +free: + WARN_ON(event); + if (fd1 >= 0) + put_unused_fd(fd1); + if (fd2 >= 0) + put_unused_fd(fd2); + kfree(user_event); + return ret; +} + +static __poll_t fop_supervisor_poll(struct file *file, poll_table *wait) +{ + struct landlock_supervisor *supervisor = file->private_data; + __poll_t mask = 0; + + poll_wait(file, &supervisor->poll_event_wq, wait); + + spin_lock(&supervisor->lock); + if (!list_empty(&supervisor->event_queue)) + mask |= POLLIN | POLLRDNORM; + spin_unlock(&supervisor->lock); + + return mask; +} + +static ssize_t fop_supervisor_write(struct file *const filp, + const char __user *const buf, + const size_t size, loff_t *const ppos) +{ + struct landlock_supervisor *supervisor = filp->private_data; + struct landlock_supervise_response response; + struct landlock_supervise_event_kernel *event; + size_t bytes_processed = 0; + bool found; + + /* We need at least one complete response */ + if (size < sizeof(response)) + return -EINVAL; + + while (bytes_processed + sizeof(response) <= size) { + if (copy_from_user(&response, buf + bytes_processed, + sizeof(response))) + return -EFAULT; + + if (response.length != sizeof(response)) + return -EINVAL; + + spin_lock(&supervisor->lock); + + /* Find the event with matching cookie */ + found = false; + list_for_each_entry(event, &supervisor->notified_events, node) { + if (event->event_id == response.cookie) { + found = true; + break; + } + } + + if (!found) { + spin_unlock(&supervisor->lock); + pr_warn("Unknown supervise event cookie: %u\n", + response.cookie); + event = NULL; + goto ret; + } + + list_del(&event->node); + spin_unlock(&supervisor->lock); + + if (WARN_ON(LANDLOCK_SUPERVISE_EVENT_HANDLED(event))) { + bytes_processed += sizeof(response); + landlock_put_supervise_event(event); + event = NULL; + continue; + } + + if (response.decision == LANDLOCK_SUPERVISE_DECISION_ALLOW) + event->state = LANDLOCK_SUPERVISE_EVENT_ALLOWED; + else if (response.decision == LANDLOCK_SUPERVISE_DECISION_DENY) + event->state = LANDLOCK_SUPERVISE_EVENT_DENIED; + else { + pr_warn("Invalid supervise event decision: %u\n", + response.decision); + goto fail_re_add; + } + + wake_up_var(event); + landlock_put_supervise_event(event); + event = NULL; + + bytes_processed += sizeof(response); + } + goto ret; + +fail_re_add: + spin_lock(&supervisor->lock); + list_add(&event->node, &supervisor->notified_events); + event = NULL; + spin_unlock(&supervisor->lock); + +ret: + WARN_ON(event); + return bytes_processed > 0 ? bytes_processed : -EINVAL; +} + static const char * event_state_to_string(enum landlock_supervise_event_state state) { @@ -338,9 +680,10 @@ static void fop_supervisor_fdinfo(struct seq_file *m, struct file *f) static const struct file_operations supervisor_fops = { .release = fop_supervisor_release, - /* TODO: read, write, poll, dup */ - .read = fop_dummy_read, - .write = fop_dummy_write, + .read = fop_supervisor_read, + .write = fop_supervisor_write, + .poll = fop_supervisor_poll, + .llseek = noop_llseek, .show_fdinfo = fop_supervisor_fdinfo, }; From patchwork Tue Mar 4 01:13:05 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tingmao Wang X-Patchwork-Id: 13999757 Received: from flow-b4-smtp.messagingengine.com (flow-b4-smtp.messagingengine.com [202.12.124.139]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7853833D8; Tue, 4 Mar 2025 01:22:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=202.12.124.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051328; cv=none; b=lTCATuOj/sVLz7BvnYPu32MILf68MkxKMX3g8ocxJH7FptN0KAdeHK9IMRBHP5JYBgb3gMFdrDiEgZPXScjDZOvOc8PSR0Rs9u+qVXqMnIZm2sPkbShi31OWEIsc2JKISVvaO98lI/bX/5GcdMJDqO0TxHPRjWEa3UaEyOfpj3M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1741051328; c=relaxed/simple; bh=p4vlPwxauIVTbVuwo1A6jAImfwXTgdK+AuGw9IdBHMw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ogDbr02P9qUujPO73JRaMZXlpd5iS7FfBHarF1/2vc9Rq+7B+2IRXnCz7t4cPs5ndQiMyMN2uf2F6lYIK+4Y3VgT5ZLz345TvgtqZ0Z7tkzRZU6edpfiE8182tnlq/UahG1fyHZePeYmQsU0QUw3VWdcqfkvue9S/30w2+gpMj4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org; spf=pass smtp.mailfrom=maowtm.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b=afwZJpm8; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b=e1URqcV4; arc=none smtp.client-ip=202.12.124.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=maowtm.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=maowtm.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=maowtm.org header.i=@maowtm.org header.b="afwZJpm8"; dkim=pass (2048-bit key) header.d=messagingengine.com header.i=@messagingengine.com header.b="e1URqcV4" Received: from phl-compute-01.internal (phl-compute-01.phl.internal [10.202.2.41]) by mailflow.stl.internal (Postfix) with ESMTP id 6D0C51D415DD; Mon, 3 Mar 2025 20:22:05 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-01.internal (MEProxy); Mon, 03 Mar 2025 20:22:05 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maowtm.org; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1741051325; x= 1741054925; bh=4wieLldKDZRs2aSCi2od3P1sUduKlu8VDZnNicEUjtc=; b=a fwZJpm85QyGCIRWyHiJSrBnF8wiKVq1vg/CyK7vFYKZWoG8a0rsbVDnx+/hwlK5E T6fGe50V35o6s2UPyoz2gQn6IUhnAUipdE0m0vijLG0Za7QRkmL+icEr4+TQ/jAa REs2c+o2UkZhlwRAB+qTg5uhKDiE6dZFX6lSqgeI5Hlrs0mlV8bjYrHQg6RyXHTe 87s9jwmWMs1ErPwgn0GiqMmA9cmHWIa5EpPlZ5vrr538hZoqjKTAHaiQQ7/UNb5G 0A0fqz2yHBWGhhl1mKFGFsOxOeqvxVdD8Wt8F+K71YUOKb7CGA5PUBs5Fjz0Jor0 MiIOa0fjDDvwr0GdfiItA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1741051325; x=1741054925; bh=4 wieLldKDZRs2aSCi2od3P1sUduKlu8VDZnNicEUjtc=; b=e1URqcV4MXKiYYhvU f2bcyGr5ehMTtX6vLOhJKkztn4vHVgC8CUNjAqxalgQeMqOF3/7Yh4AWN0waRIqI acEEFdOWxHbhIbuVKlWSF1CPch00AT7AEMIAEedjenmHqZX3ExyYnCp83mGZWGCr 9Xp1DZRLjrFR7XwcHEnlxZz6kTbUaosn9fMVPLoxtwRtnjjSA3uqVoZimeDWjTo2 /4X8QsQ62alR/OJNpihPdu08natMDswv8c1r2zy6YdiGY4d5JbSpaE74qBbTrFqN vPKRAu/xmWaRKp1xxWzGCYffo0fPfVghVFe31TJwlk+6eBdgU6cPdkhUkfDQeIWa KoHRA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgddutddtieekucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnecujfgurhephffvvefufffkofgjfhgggfestdekredtredt tdenucfhrhhomhepvfhinhhgmhgrohcuhggrnhhguceomhesmhgrohifthhmrdhorhhgqe enucggtffrrghtthgvrhhnpeeuuddthefhhefhvdejteevvddvteefffegteetueegueel jeefueekjeetieeuleenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehmsehmrghofihtmhdrohhrghdpnhgspghrtghpthhtohepledpmhhouggv pehsmhhtphhouhhtpdhrtghpthhtohepmhhitgesughighhikhhougdrnhgvthdprhgtph htthhopehgnhhorggtkhesghhoohhglhgvrdgtohhmpdhrtghpthhtohepjhgrtghksehs uhhsvgdrtgiipdhrtghpthhtohepmhesmhgrohifthhmrdhorhhgpdhrtghpthhtoheplh hinhhugidqshgvtghurhhithihqdhmohguuhhlvgesvhhgvghrrdhkvghrnhgvlhdrohhr ghdprhgtphhtthhopegrmhhirhejfehilhesghhmrghilhdrtghomhdprhgtphhtthhope hrvghpnhhophesghhoohhglhgvrdgtohhmpdhrtghpthhtoheplhhinhhugidqfhhsuggv vhgvlhesvhhgvghrrdhkvghrnhgvlhdrohhrghdprhgtphhtthhopehthigthhhosehthi gthhhordhpihiiiigr X-ME-Proxy: Feedback-ID: i580e4893:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 3 Mar 2025 20:22:03 -0500 (EST) From: Tingmao Wang To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G?= =?utf-8?q?=C3=BCnther_Noack?= , Jan Kara Cc: Tingmao Wang , linux-security-module@vger.kernel.org, Amir Goldstein , Matthew Bobrowski , linux-fsdevel@vger.kernel.org, Tycho Andersen Subject: [RFC PATCH 9/9] Enhance the sandboxer example to support landlock-supervise Date: Tue, 4 Mar 2025 01:13:05 +0000 Message-ID: <9dc2b112c4be1aadff612b226c603db66ef79955.1741047969.git.m@maowtm.org> X-Mailer: git-send-email 2.48.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This is perhaps a bit overengineered with the ppoll on 5 different fds, but it makes sure the sandboxed child can't try to print anything to the terminal from a different thread while an access request is pending (otherwise it could trick the user by printing over the request text). This also makes sure inputs are directed to the right place (the child when no prompt, or the sandboxer itself when an access request is shown). But even with that, I'm not claiming this "sandbox" with supervise mode is in any way production quality. It's intended as a PoC. Signed-off-by: Tingmao Wang --- samples/landlock/sandboxer.c | 759 ++++++++++++++++++++++++++++++++++- 1 file changed, 739 insertions(+), 20 deletions(-) diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index 07fab2ef534e..4a6a0d74c614 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -24,10 +24,16 @@ #include #include #include +#include +#include +#include +#include +#include +#include #ifndef landlock_create_ruleset static inline int -landlock_create_ruleset(const struct landlock_ruleset_attr *const attr, +landlock_create_ruleset(struct landlock_ruleset_attr *const attr, const size_t size, const __u32 flags) { return syscall(__NR_landlock_create_ruleset, attr, size, flags); @@ -58,6 +64,7 @@ static inline int landlock_restrict_self(const int ruleset_fd, #define ENV_TCP_BIND_NAME "LL_TCP_BIND" #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" #define ENV_SCOPED_NAME "LL_SCOPED" +#define ENV_SUPERVISE "LL_SUPERVISE" #define ENV_DELIMITER ":" static int str2num(const char *numstr, __u64 *num_dst) @@ -278,24 +285,30 @@ static bool check_ruleset_scope(const char *const env_var, LANDLOCK_ACCESS_FS_READ_FILE | \ LANDLOCK_ACCESS_FS_READ_DIR) -#define ACCESS_FS_ROUGHLY_WRITE ( \ - LANDLOCK_ACCESS_FS_WRITE_FILE | \ - LANDLOCK_ACCESS_FS_REMOVE_DIR | \ - LANDLOCK_ACCESS_FS_REMOVE_FILE | \ +#define ACCESS_FS_ROUGHLY_CREATE ( \ LANDLOCK_ACCESS_FS_MAKE_CHAR | \ LANDLOCK_ACCESS_FS_MAKE_DIR | \ LANDLOCK_ACCESS_FS_MAKE_REG | \ LANDLOCK_ACCESS_FS_MAKE_SOCK | \ LANDLOCK_ACCESS_FS_MAKE_FIFO | \ LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ - LANDLOCK_ACCESS_FS_MAKE_SYM | \ + LANDLOCK_ACCESS_FS_MAKE_SYM) + +#define ACCESS_FS_ROUGHLY_REMOVE ( \ + LANDLOCK_ACCESS_FS_REMOVE_DIR | \ + LANDLOCK_ACCESS_FS_REMOVE_FILE) + +#define ACCESS_FS_ROUGHLY_WRITE ( \ + LANDLOCK_ACCESS_FS_WRITE_FILE | \ + ACCESS_FS_ROUGHLY_CREATE | \ + ACCESS_FS_ROUGHLY_REMOVE | \ LANDLOCK_ACCESS_FS_REFER | \ LANDLOCK_ACCESS_FS_TRUNCATE | \ LANDLOCK_ACCESS_FS_IOCTL_DEV) /* clang-format on */ -#define LANDLOCK_ABI_LAST 6 +#define LANDLOCK_ABI_LAST 7 #define XSTR(s) #s #define STR(s) XSTR(s) @@ -321,6 +334,7 @@ static const char help[] = "* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n" " - \"a\" to restrict opening abstract unix sockets\n" " - \"s\" to restrict sending signals\n" + "* " ENV_SUPERVISE ": set to 1 to enable supervisor mode\n" "\n" "Example:\n" ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" " @@ -335,14 +349,22 @@ static const char help[] = /* clang-format on */ +int verbose_exec(const char *cmd_path, char *const *cmd_argv, + char *const *envp); +int interactive_sandboxer(int supervisor_fd, int child_stdin, int child_stdout, + int child_stderr, pid_t child_pid); + int main(const int argc, char *const argv[], char *const *const envp) { const char *cmd_path; char *const *cmd_argv; - int ruleset_fd, abi; + int ruleset_fd = -1, supervisor_fd = -1, abi; char *env_port_name; __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ, access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; + bool supervise = false; + __u32 flags; + char *env_supervise; struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = access_fs_rw, @@ -350,6 +372,8 @@ int main(const int argc, char *const argv[], char *const *const envp) LANDLOCK_ACCESS_NET_CONNECT_TCP, .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL, + .supervisor_fd = 0, + .pad = 0, }; if (argc < 2) { @@ -357,6 +381,11 @@ int main(const int argc, char *const argv[], char *const *const envp) return 1; } + env_supervise = getenv(ENV_SUPERVISE); + if (env_supervise && strcmp(env_supervise, "1") == 0) { + supervise = true; + } + abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); if (abi < 0) { const int err = errno; @@ -422,6 +451,10 @@ int main(const int argc, char *const argv[], char *const *const envp) /* Removes LANDLOCK_SCOPE_* for ABI < 6 */ ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL); + __attribute__((fallthrough)); + case 6: + /* Removes supervisor mode for ABI < 7 */ + supervise = false; fprintf(stderr, "Hint: You should update the running kernel " "to leverage Landlock features " @@ -456,12 +489,31 @@ int main(const int argc, char *const argv[], char *const *const envp) if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr)) return 1; - ruleset_fd = - landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + flags = 0; + if (supervise) + flags |= LANDLOCK_CREATE_RULESET_SUPERVISE; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), flags); if (ruleset_fd < 0) { perror("Failed to create a ruleset"); return 1; } + if (supervise) { + supervisor_fd = ruleset_attr.supervisor_fd; + if (supervisor_fd < 0) { + fprintf(stderr, "supervisor_fd is invalid"); + return 1; + } + if (supervisor_fd == 0) { + fprintf(stderr, "supervisor_fd not set by kernel"); + return 1; + } + } else if (ruleset_attr.supervisor_fd != 0) { + fprintf(stderr, + "supervisor_fd should not be set by kernel, but it is not 0"); + return 1; + } if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) { goto err_close_ruleset; @@ -483,23 +535,690 @@ int main(const int argc, char *const argv[], char *const *const envp) perror("Failed to restrict privileges"); goto err_close_ruleset; } - if (landlock_restrict_self(ruleset_fd, 0)) { - perror("Failed to enforce ruleset"); - goto err_close_ruleset; - } - close(ruleset_fd); cmd_path = argv[1]; cmd_argv = argv + 1; + + if (!supervise) { + if (landlock_restrict_self(ruleset_fd, 0)) { + perror("Failed to enforce ruleset"); + goto err_close_ruleset; + } + close(ruleset_fd); + verbose_exec(cmd_path, cmd_argv, envp); + } else { + pid_t child; + int child_stdin_pipe[2], child_stdout_pipe[2], + child_stderr_pipe[2]; + // read from [0], write to [1] + if (pipe(child_stdin_pipe) || pipe(child_stdout_pipe) || + pipe(child_stderr_pipe)) { + perror("Failed to create pipes"); + goto err_close_ruleset; + } + child = fork(); + if (child < 0) { + perror("Failed to fork"); + goto err_close_ruleset; + } + if (child == 0) { + close(supervisor_fd); + + if (landlock_restrict_self(ruleset_fd, 0)) { + perror("Failed to enforce ruleset"); + goto err_close_ruleset; + } + + close(child_stdin_pipe[1]); + close(child_stdout_pipe[0]); + close(child_stderr_pipe[0]); + if (dup2(child_stdin_pipe[0], STDIN_FILENO) < 0 || + dup2(child_stdout_pipe[1], STDOUT_FILENO) < 0 || + dup2(child_stderr_pipe[1], STDERR_FILENO) < 0) { + perror("Failed to redirect child I/O"); + exit(1); + } + close(child_stdin_pipe[0]); + close(child_stdout_pipe[1]); + close(child_stderr_pipe[1]); + + close(ruleset_fd); + verbose_exec(cmd_path, cmd_argv, envp); + } else { + close(ruleset_fd); + close(child_stdin_pipe[0]); + close(child_stdout_pipe[1]); + close(child_stderr_pipe[1]); + return interactive_sandboxer(supervisor_fd, + child_stdin_pipe[1], + child_stdout_pipe[0], + child_stderr_pipe[0], + child); + } + } + +err_close_ruleset: + close(ruleset_fd); + return 1; +} + +int verbose_exec(const char *cmd_path, char *const *cmd_argv, char *const *envp) +{ fprintf(stderr, "Executing the sandboxed command...\n"); execvpe(cmd_path, cmd_argv, envp); + int err = errno; fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, - strerror(errno)); + strerror(err)); fprintf(stderr, "Hint: access to the binary, the interpreter or " "shared libraries may be denied.\n"); - return 1; + return err; +} -err_close_ruleset: - close(ruleset_fd); - return 1; +enum SandboxAccessType { + ACCESS_READ, + ACCESS_READWRITE, + ACCESS_CREATE, + ACCESS_REMOVE, +}; + +struct context { + int supervisor_fd; + char **allowed_paths; + size_t num_allowed_paths; +}; + +static int f_set_noblock(int fd) +{ + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + perror("Failed to get flags"); + return -1; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + perror("Failed to set flags"); + return -1; + } + return 0; +} + +static int write_all(int fd, const char *buf, size_t count) +{ + while (count > 0) { + ssize_t written = write(fd, buf, count); + if (written < 0) { + return written; + } + count -= written; + buf += written; + } + return 0; +} + +static int readlink_fd_s(int fd, char *buf, size_t buf_len) +{ + if (buf_len == 0) { + errno = EINVAL; + return -1; + } + char procfd[100]; + snprintf(procfd, sizeof(procfd), "/proc/self/fd/%d", fd); + ssize_t len = readlink(procfd, buf, buf_len - 1); + if (len < 0) { + return -1; + } + buf[len] = '\0'; + return len; +} + +static bool show_sandbox_prompt_fs(enum SandboxAccessType access, + const char *file1, const char *file2, + int pid, const char *comm, const char *exe, + struct context *context) +{ + const char *access_kv; + switch (access) { + case ACCESS_READ: + access_kv = "read"; + break; + case ACCESS_READWRITE: + access_kv = "read/write"; + break; + case ACCESS_CREATE: + access_kv = "create"; + break; + case ACCESS_REMOVE: + access_kv = "remove"; + break; + default: + abort(); + return false; + } + if (isatty(STDIN_FILENO)) { + tcflush(STDIN_FILENO, TCIOFLUSH); + } + fprintf(stderr, + "------------- Sandboxer access request -------------\n"); + fprintf(stderr, "Process %s[%d] (%s) wants to %s\n %s\n", comm, pid, + exe, access_kv, file1); + if (file2) { + fprintf(stderr, " %s\n", file2); + } + bool allow = false; + while (true) { + char answer[10]; + fprintf(stderr, "(y)es/(a)lways/(n)o > "); + fflush(stderr); + int rc = read(STDIN_FILENO, answer, sizeof(answer)); + if (rc < 0) { + perror("Failed to read answer"); + break; + } + if (rc == 0) { + break; + } + answer[rc] = '\0'; + if (strcmp(answer, "y\n") == 0) { + allow = true; + break; + } else if (strcmp(answer, "a\n") == 0) { + allow = true; + /* +2 in case file2 is also set */ + context->allowed_paths = + realloc(context->allowed_paths, + (context->num_allowed_paths + 2) * + sizeof(char *)); + if (!context->allowed_paths) { + abort(); + } + char *dup_str = strdup(file1); + if (!dup_str) { + abort(); + } + context->allowed_paths[context->num_allowed_paths] = + dup_str; + context->num_allowed_paths++; + + if (file2) { + dup_str = strdup(file2); + if (!dup_str) { + abort(); + } + context->allowed_paths + [context->num_allowed_paths] = dup_str; + context->num_allowed_paths++; + } + break; + } else if (strcmp(answer, "n\n") == 0) { + allow = false; + break; + } else { + fprintf(stderr, + "Please answer \"y\", \"a\", or \"n\"\n"); + } + } + fprintf(stderr, + "----------------------------------------------------\n"); + return allow; +} + +static bool show_sandbox_prompt_network(__u16 port, struct context *context) +{ + /* TODO: unimplemented in kernel */ + return true; +} + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +static bool path_join(char *dest_buf, size_t dest_buf_len, const char *last) +{ + if (dest_buf_len <= 1) { + return false; + } + size_t last_len = strlen(last); + size_t dest_len = strnlen(dest_buf, dest_buf_len); + if (dest_len == 1 && dest_buf[0] == '/') { + dest_buf[0] = '\0'; + dest_len = 0; + } + size_t dest_space = dest_buf_len - dest_len; + if (dest_space <= 1) { + return false; + } + if (dest_space == 2) { + dest_buf[dest_len] = '/'; + dest_buf[dest_len + 1] = '\0'; + return false; + } + size_t copy_count = min(dest_space - 2, last_len); + dest_buf[dest_len] = '/'; + memcpy(dest_buf + dest_len + 1, last, copy_count); + dest_buf[dest_len + 1 + copy_count] = '\0'; + return copy_count == last_len; +} + +static int process_event(struct landlock_supervise_event *evt, + struct context *context) +{ + char *target_path_1 = NULL; + char *target_path_2 = NULL; + char *comm = NULL; + char *exe = NULL; + int pid; + int fd = -1; + ssize_t len; + enum SandboxAccessType access = -1; + char proc_exe[100], proc_comm[100]; + struct landlock_supervise_response response; + bool allow = false; + int ret = 0; + int supervisor_fd = context->supervisor_fd; + + memset(&response, 0, sizeof(response)); + + if (((uintptr_t)evt) % __alignof__(struct landlock_supervise_event) != + 0) { + /* + * Check that the kernel hasn't messed up given we're + * reading an array of varable length struct + */ + fprintf(stderr, "evt = %p is badly aligned\n", evt); + abort(); + } + + switch (evt->hdr.type) { + case LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS: + if (evt->fd1 != -1) { + target_path_1 = malloc(PATH_MAX); + if (!target_path_1) { + abort(); + } + if (readlink_fd_s(evt->fd1, target_path_1, PATH_MAX) < + -1) { + close(evt->fd1); + perror("Failed to readlink"); + ret = -1; + goto ret; + } + close(evt->fd1); + } else { + fprintf(stderr, "fd1 is -1 which should not happen."); + abort(); + } + if (evt->fd2 != -1) { + target_path_2 = malloc(PATH_MAX); + if (!target_path_2) { + abort(); + } + if (readlink_fd_s(evt->fd2, target_path_2, PATH_MAX) < + -1) { + perror("Failed to readlink"); + close(evt->fd2); + ret = -1; + goto ret; + } + close(evt->fd2); + } + if (evt->destname[0] != 0) { + if (evt->fd2 != -1) { + path_join(target_path_2, PATH_MAX, + evt->destname); + } else { + path_join(target_path_1, PATH_MAX, + evt->destname); + } + } + if (evt->access_request & ACCESS_FS_ROUGHLY_CREATE) { + access = ACCESS_CREATE; + } else if (evt->access_request & ACCESS_FS_ROUGHLY_REMOVE) { + access = ACCESS_REMOVE; + } else if (evt->access_request & ACCESS_FS_ROUGHLY_WRITE) { + access = ACCESS_READWRITE; + } else { + access = ACCESS_READ; + } + + if (strcmp(target_path_1, "/dev/tty") == 0) { + /* + * Deny TTY access to bash, as it messes with the + * supervisor input, causing the supervisor to + * receive SIGTTIN + */ + goto response; + } + + for (size_t i = 0; i < context->num_allowed_paths; i++) { + if (strcmp(target_path_1, context->allowed_paths[i]) == + 0) { + allow = true; + break; + } + } + break; + case LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS: + /* No pre-processing needed */ + break; + default: + fprintf(stderr, "Unknown event type: %d\n", evt->hdr.type); + ret = -1; + break; + } + + pid = evt->accessor; + snprintf(proc_exe, sizeof(proc_exe), "/proc/%d/exe", pid); + exe = malloc(PATH_MAX); + if (!exe) { + abort(); + } + len = readlink(proc_exe, exe, PATH_MAX - 1); + if (len < 0) { + perror("Failed to readlink proc exe"); + return -1; + } + exe[len] = '\0'; + snprintf(proc_comm, sizeof(proc_comm), "/proc/%d/comm", pid); + comm = malloc(PATH_MAX); + if (!comm) { + abort(); + } + fd = open(proc_comm, O_RDONLY); + if (fd < 0) { + snprintf(comm, PATH_MAX, "???"); + } else { + len = read(fd, comm, PATH_MAX - 1); + if (len < 0) { + snprintf(comm, PATH_MAX, "???"); + } else { + comm[len] = '\0'; + if (len > 0 && comm[len - 1] == '\n') { + comm[len - 1] = '\0'; + } + } + close(fd); + } + + switch (evt->hdr.type) { + case LANDLOCK_SUPERVISE_EVENT_TYPE_FS_ACCESS: + if (!allow) { + allow = show_sandbox_prompt_fs(access, target_path_1, + target_path_2, pid, comm, + exe, context); + } + break; + case LANDLOCK_SUPERVISE_EVENT_TYPE_NET_ACCESS: + allow = show_sandbox_prompt_network(evt->port, context); + break; + } + +response: + /* Prepare and send response to the kernel */ + response.length = sizeof(response); + response.decision = allow ? LANDLOCK_SUPERVISE_DECISION_ALLOW : + LANDLOCK_SUPERVISE_DECISION_DENY; + response.cookie = evt->hdr.cookie; + + if (write(supervisor_fd, &response, sizeof(response)) != + sizeof(response)) { + perror("Failed to write supervisor response"); + ret = -1; + } + +ret: + free(target_path_1); + free(target_path_2); + free(comm); + free(exe); + return ret; +} + +static int process_events(void *data, size_t data_len, struct context *context) +{ + while (data_len > 0) { + struct landlock_supervise_event *evt; + int rc; + if (data_len < sizeof(evt->hdr)) { + fprintf(stderr, + "Too few bytes for a event header - got %zu left, need %zu.", + data_len, sizeof(evt->hdr)); + return -EINVAL; + } + evt = data; + if (evt->hdr.length > data_len) { + fprintf(stderr, + "Length from event header is greater than remaining data."); + return -EINVAL; + } + rc = process_event(evt, context); + if (rc < 0) { + return rc; + } + data_len -= evt->hdr.length; + data += evt->hdr.length; + } + return 0; +} + +int interactive_sandboxer(int supervisor_fd, int child_stdin, int child_stdout, + int child_stderr, pid_t child_pid) +{ + char *write_buf = NULL; + size_t write_buf_len = 0; + + size_t io_buf_len = 4096; + char *io_buf = malloc(io_buf_len); + if (!io_buf) { + fprintf(stderr, "Failed to allocate I/O buffer"); + return -1; + } + + int status = 0; + + struct pollfd pfds[5] = { + { .fd = STDIN_FILENO, .events = POLLIN }, + { .fd = child_stdout, .events = POLLIN }, + { .fd = child_stderr, .events = POLLIN }, + { .fd = supervisor_fd, .events = POLLIN }, + { .fd = child_stdin, .events = POLLOUT }, + }; + const int pfd_idx_stdin = 0; + const int pfd_idx_child_stdout = 1; + const int pfd_idx_child_stderr = 2; + const int pfd_idx_supervisor = 3; + const int pfd_idx_child_stdin = 4; + const int poll_len = 5; + + struct context context = { + .supervisor_fd = supervisor_fd, + .allowed_paths = NULL, + .num_allowed_paths = 0, + }; + + bool child_stdin_closed = false; + + /* + * Don't deadlock by us trying to write to child, and child + * waiting to write to us. + */ + f_set_noblock(child_stdin); + + /* Don't get killed by SIGPIPE when child closes stdout/err */ + signal(SIGPIPE, SIG_IGN); + + while (1) { + if (write_buf_len > 0 && !child_stdin_closed) { + pfds[pfd_idx_child_stdin].fd = child_stdin; + } else { + pfds[pfd_idx_child_stdin].fd = -1; + } + + for (int i = 0; i < poll_len; i++) { + pfds[i].revents = 0; + } + + if (ppoll(pfds, poll_len, NULL, NULL) < 0) { + if (errno != EINTR) { + perror("ppoll"); + goto err_kill_child; + } + } + + if (pfds[0].revents & POLLIN) { + /* + * Our stdin -> temp buffer for child's stdin. + * Need to do this before handling any supervisor + * events so that inputs intended for the child is + * not interperted as user decision. + */ + const int read_len = 4096; + write_buf = + realloc(write_buf, write_buf_len + read_len); + if (!write_buf) { + fprintf(stderr, + "Failed to realloc write buffer\n"); + goto err_kill_child; + } + ssize_t count = read(STDIN_FILENO, + write_buf + write_buf_len, + read_len); + if (count > 0) { + write_buf_len += count; + } else if (count == 0) { + /* Our stdin is closed. Don't read from it anymore. */ + pfds[pfd_idx_stdin].fd = -1; + } else { + perror("Failed to read from stdin"); + goto err_kill_child; + } + } + + if (write_buf_len > 0) { + /* Attempt to write any outstanding stdin to child */ + ssize_t written = + write(child_stdin, write_buf, write_buf_len); + if (written > 0) { + if (written > write_buf_len) { + abort(); + } else if (written == write_buf_len) { + write_buf_len = 0; + } else { + memmove(write_buf, write_buf + written, + write_buf_len - written); + write_buf_len -= written; + } + } else { + if (errno == EPIPE) { + close(child_stdin); + child_stdin_closed = true; + pfds[pfd_idx_child_stdin].fd = -1; + write_buf_len = 0; + } else if (errno != EAGAIN) { + perror("Failed to write to child stdin"); + goto err_kill_child; + } + } + } + + if (pfds[pfd_idx_stdin].fd == -1 && write_buf_len == 0) { + /* We can safely close child's stdin now */ + close(child_stdin); + child_stdin_closed = true; + pfds[pfd_idx_child_stdin].fd = -1; + } + + if (pfds[pfd_idx_child_stdout].revents & POLLIN) { + /* Child stdout -> our stdout */ + ssize_t count = read(child_stdout, io_buf, io_buf_len); + if (count > 0) { + if (write_all(STDOUT_FILENO, io_buf, count) < + 0) { + perror("Failed to write to stdout"); + goto err_kill_child; + } + } else if (count == 0 || + (count < 0 && errno == EPIPE)) { + close(child_stdout); + pfds[pfd_idx_child_stdout].fd = -1; + } else if (count < 0 && errno != EAGAIN) { + perror("Failed to read from child stdout"); + goto err_kill_child; + } + } + + if (pfds[2].revents & POLLIN) { + /* Child stderr -> our stderr */ + ssize_t count = read(child_stderr, io_buf, io_buf_len); + if (count > 0) { + if (write_all(STDERR_FILENO, io_buf, count) < + 0) { + perror("Failed to write to stderr"); + goto err_kill_child; + } + } else if (count == 0 || + (count < 0 && errno == EPIPE)) { + close(child_stderr); + pfds[pfd_idx_child_stderr].fd = -1; + } else if (count < 0 && errno != EAGAIN) { + perror("Failed to read from child stderr"); + goto err_kill_child; + } + } + + if (waitpid(child_pid, &status, WNOHANG) == child_pid) { + /* + * Write out any remaining child stdout/stderr. + * If child died, read would just return EOF. + */ + while (1) { + ssize_t count = + read(child_stdout, io_buf, io_buf_len); + if (count > 0) + write_all(STDOUT_FILENO, io_buf, count); + else + break; + } + while (1) { + ssize_t count = + read(child_stderr, io_buf, io_buf_len); + if (count > 0) + write_all(STDERR_FILENO, io_buf, count); + else + break; + } + return WIFEXITED(status) ? WEXITSTATUS(status) : 1; + } + + if (pfds[pfd_idx_supervisor].revents) { +retry: + ssize_t count = read(supervisor_fd, io_buf, io_buf_len); + if (count > 0) { + process_events(io_buf, count, &context); + } else if (count == 0) { + fprintf(stderr, + "Unexpected EOF on supervisor fd\n"); + goto err_kill_child; + } else if (count < 0 && errno != EAGAIN) { + if (errno == EINVAL) { + io_buf_len *= 2; + io_buf = realloc(io_buf, io_buf_len); + if (!io_buf) { + fprintf(stderr, + "Failed to realloc I/O buffer\n"); + goto err_kill_child; + } + fprintf(stderr, + "Got EINVAL - possibly event too big. Realloced I/O buffer to %zu\n", + io_buf_len); + goto retry; + } + perror("Failed to read from supervisor"); + goto err_kill_child; + } + } + } + +err_kill_child: + close(supervisor_fd); + kill(child_pid, SIGTERM); + return -1; }