From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f169.google.com (mail-yw1-f169.google.com [209.85.128.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5290E3630B5 for ; Fri, 29 May 2026 01:52:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019545; cv=none; b=gpmybBd3VlgcHONsOtOwwa1fbmHD73wtXxpjvpaIxJiG9HZUOEn58H/xMSLMf8pqjXjqROaAcQju5zYGO7nBWSxgYhWeuScUt/r+jE8o3Nyf1CAaXSjf105LQ/TubldFPK6/lFqnG09D+5QQL/lpk+uyfkXYAcrf9D3+iDFxHqo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019545; c=relaxed/simple; bh=7Tcy8Vd2YFqPv1kuhiGHki5kQkFlk7YAgNHoIyhBMxw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=DkAU4h8zdys5omFThG3Ba6TgcP16fq84xOgyiS4kVvsxt21GLTC7iXXh6GUemFvDyby/YYVsSqHjfuZvgzMKARYWK/P6DxG0qV6tWTte93mxSuvY4v8f46msCiSD6u7A/Ez/fhy/ZvL7LyJ7OyYG5WJWUnQL72MRuEEewjWEL3M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=IO8vjeVS; arc=none smtp.client-ip=209.85.128.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="IO8vjeVS" Received: by mail-yw1-f169.google.com with SMTP id 00721157ae682-7bde9d73678so140830577b3.0 for ; Thu, 28 May 2026 18:52:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019543; x=1780624343; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=kzvSYVigel/B+h2nExs5/MzF6RaaZLzOGje0ZHns2RU=; b=IO8vjeVS8Iw154s8kd5C6TiAnPl6r2y6oFXfG8hL66mxg4ADOQPXO0ntj8dc2RtPen DZ2NYAtvrGDkOdoQHiU/4xuwIVQwMOfjyGu8NMZ/PKys2ghk/BmCzm3lhZC5douv0P58 sWdAd05p7o7F5iHzTfmhXnmb6cmrHLqkrPTo/y3pcc21RSvKEJfwhEUYocdFWftP/r79 nBNhnZexjY+TB5SPgK0K4cYYDM3zsA+1qHb0K61DxIJNVUKfWI5OIIdtLHKSwU5ihEGm x6EFEv1X25JgvyQHZ48JN4iMJ6SHpF9U0w2aXN96XlYgE3DeqCN0QsvJuWWSSS9zmmKi EyJQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019543; x=1780624343; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=kzvSYVigel/B+h2nExs5/MzF6RaaZLzOGje0ZHns2RU=; b=G6I5+ktAvAHi3u7cEW0hBzpki3vq5dpEK1+9spvME1Q9QR66vv8eKo1M3jRC7sCd9q r7S68Ol4Vs5Vez02iy0d/daGjh1DPYVSlNEg1iSiR9zDCZeiWFvn1hRQXXN5iP660t8j V8KIq1uLJSMm9CQSB8hiC0ExGB/h0BU1K51iY/B0Wmzd2hlU+H/3LAtyKOHDnMNsh32o iPNT4c94vd5pzDbRLLmL30JHCRMqKaOAchqMKLfDBwVpIdDLWFSnAoRNbgJr2iLVjf18 J+bnlMWFubn1MRHEOAXF1w7f4XFXHAa9hp1WuQhh45bHBhXV4zWTJDTixlBr6ZXHGB2S HGtw== X-Gm-Message-State: AOJu0YyvCK0OGWRKNb7TEYp+TjwnCPjCAlDrAZGmOygonEJriDT3f5Ug QO2rCo1oTh2/bGzW6wJB7z/h6LvLkqaxoHRQetAP+CJbBhGNnzow1bgT X-Gm-Gg: Acq92OGcTXIwaNpaGiWoj8ShGTTD2+F7HI5daAng5EGSMgTTGzrXY8YBAfaFK0KVrH0 g9sryb11amTsxTjb3pptUXdJCT5rGr2mfI+IMhSfIi+COuYKEsGxmnRvoGnPpjCjFRIJ66Jeh3q WqOuNJ12agop7JKcab6HLckjB5AaLDiLxXbXU3AZ8KIkIMDaqGKfxgRa7AKius6UCHr5ZhcxjKG A13ymmiLrnlUCDhJ5UmJSLFnDPsd6tIiJEFsklDFPUwJx+P02bMAuAprfxYMeLvWbkDLCQXWYhO Vcn14plVdxmVh4wWhdbs/3bMxqfFnOZx/le3C+acPF2Lv8WSmdN9JXz5XGOqO17g/ySoEgTPhjj tQJr+2MwaNFpoQpzpnmOA+AyVqhJZvtoTay0y9N4QXeV38N+4ZXCaH0fER51Z58GS1NuHyA1Jbq J0FTgCJIQOlsDESLX26NZDUwsKDDoMjDGoL8HGzB0vJxgvqDQTDh1xTuOnArXjFDG9Z2TQM65px 4NFqxb/fTM= X-Received: by 2002:a05:690c:c24a:b0:7b2:9347:7ba7 with SMTP id 00721157ae682-7de334bfec0mr7343957b3.22.1780019543380; Thu, 28 May 2026 18:52:23 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:23 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess , Tingmao Wang Subject: [PATCH v8 01/10] landlock: Add landlock_walk_path_up() helper Date: Thu, 28 May 2026 21:52:00 -0400 Message-ID: <20260529015210.500291-2-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In preparation for centralizing path-walk logic, add landlock_walk_path_up(), which moves @path one step toward the VFS root. Its return value indicates whether the new position is an internal mount point, the real root, or neither (i.e. the caller should continue walking). No functional change intended. Cc: Tingmao Wang Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Reworded commit message; no code changes. security/landlock/fs.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 3b71f569a8f9..8e75583c3ca7 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -320,6 +320,38 @@ static struct landlock_object *get_inode_object(struct= inode *const inode) LANDLOCK_ACCESS_FS_RESOLVE_UNIX) /* clang-format on */ =20 +/** + * enum landlock_walk_result - Result codes for landlock_walk_path_up() + * @LANDLOCK_WALK_CONTINUE: Path is now neither the real root nor an inter= nal mount point. + * @LANDLOCK_WALK_STOP_REAL_ROOT: Path has reached the real VFS root. + * @LANDLOCK_WALK_INTERNAL: Path has reached an internal mount point. + */ +enum landlock_walk_result { + LANDLOCK_WALK_CONTINUE, + LANDLOCK_WALK_STOP_REAL_ROOT, + LANDLOCK_WALK_INTERNAL, +}; + +static enum landlock_walk_result landlock_walk_path_up(struct path *const = path) +{ + struct dentry *old; + + while (path->dentry =3D=3D path->mnt->mnt_root) { + if (!follow_up(path)) + return LANDLOCK_WALK_STOP_REAL_ROOT; + } + old =3D path->dentry; + if (unlikely(IS_ROOT(old))) { + if (likely(path->mnt->mnt_flags & MNT_INTERNAL)) + return LANDLOCK_WALK_INTERNAL; + path->dentry =3D dget(path->mnt->mnt_root); + } else { + path->dentry =3D dget_parent(old); + } + dput(old); + return LANDLOCK_WALK_CONTINUE; +} + /* * @path: Should have been checked by get_path_from_fd(). */ --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f177.google.com (mail-yw1-f177.google.com [209.85.128.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 21A0D361640 for ; Fri, 29 May 2026 01:52:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019550; cv=none; b=QGuNxaPb3I/kZuCPuf0iZNifugR+6TolxlWkLUgOIv+2q7j3wmKVeJfbeax9d630origfkzbZ6RhU/SSiGMnHbjIiitpLcmofI/npj+fqmA+6r446PdQsSWwAAACU7h0B2JHYBjhqlRk7/+QafXUiwlXib9k5Xs40e8vUuYleVQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019550; c=relaxed/simple; bh=IJxeqyvQtrG7azVvLM4HbRy2GIShu6mc8HVuzDsthyw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MJE9rNFu/KW5St02CiOSFYQAdTkSXTVZcQPNaZY1LpnmAtJKcen79ISbCGA1iiEw4tOYsI+wG8PNTssgKGtLrFctID7bxOJ8mFX5YYmSMm15K1p6s/K+mjkl1m7dNCp+WFkZbHjuTZQHR97oB/cDGflaWhXkS9P2XuOjMXBCXEs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=gd+4g6up; arc=none smtp.client-ip=209.85.128.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="gd+4g6up" Received: by mail-yw1-f177.google.com with SMTP id 00721157ae682-7c58e6eb2c8so136930527b3.1 for ; Thu, 28 May 2026 18:52:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019546; x=1780624346; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2kEiKlLtYvS3U2wrQqnl+tM6yv07t9VLzyTDJ7RzJ6w=; b=gd+4g6upVDq4eexNco3Rm2m3FKLk8XuaCqEByPSu+BRX409nb6FrBenv+mXhQ2gO/R gbLEQOHYgYZmCwWpFlnvrwLquUCGqQXllte6vuDebZStD74acD8phUQPl9ZRwSiYCaO5 Im8kTVOIAHaXVQBCU/qKSFcArTmZZIZyxcoBKupfgpPdhKK6ZGQ3/RiWtWfjnAfEa4eD 0tpLemoLoeWKfOjOm3BcMM5/1arJHEZv7NUtwkylHtQzRZQ2u2c6g01z1G/eEMZp7jm0 3k9khnb8zVnzZTVVoLrg/iAbUOlWGCn5mVIDeB6IfSRwHhfZuGoYR3t58YBL6CsfFKzx FqwQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019546; x=1780624346; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=2kEiKlLtYvS3U2wrQqnl+tM6yv07t9VLzyTDJ7RzJ6w=; b=alUBXmOUr0Z1WF0eiATSP2pCjYQOcncdPF7Y/LfzxgowOUwkeQjRkmLqC97D0+tcfL 93Q18ckKaNLp143pxw2+xk9Z5nn6Yg5lEk44K4NG/e6btKfholg3mupA71fnyS16ZTew KlOTYnxS7A1TrOVBhlbOz9HiHJ9mrNaMvVJlqvfimtpRp0VdHwtLibEcK9ZMK+lDXmcQ v/P602lhedd0I4/S1zQfBgWQHxQCDva4M/ww7HpmjeT6mTfkmPR7y3RGILBup2h5ONqP w/rTYcQJ+eQL1TSOAKhUqA2nW0v+nyl3wwngMJKV/8DTyl5QH589F89evTOGJrXcmE5N lFtA== X-Gm-Message-State: AOJu0YzmFFHex1COR7RFy3G1so+1zUlMoRH6mbl0IyRInhUS20NPh2XB Z6f0g54YQrWLajpY26+x/eo30ocF3DxuKkk/jyiBvmZQ2v+3lR0pHJK5QjRYmQ== X-Gm-Gg: Acq92OF8XeRbanDbHN4LJL/1TNnw6PFzgaj0WoFbRYy61qvw19eX8y7uEJlGGLbg7da Yuv6v3c+536Gw4LcbGuZuU7vUK2x7B7Zq3joRwsEPMQ9OJ9p/pFzM/waYcSFTu0FmDo2RvDimqL /cFa5uMISvdWv+bbhD9EWW+I/CbomdtyLv7muSjR4dqUTVulMR+Jc2JK58wREswJqKbW5H8bGr2 AhNEuC3dLVM91pA9mmDBjLPQHE9lnE/x9tMFkW7CHcE4Ok4+/9LyKvR6FyZGEy5aE2FE+dgSp1Q AHdLDmJxtcYMBZp6WTtm7/VY0ZGL9QrlZr/m5cvYU3mz28S8OdVzS+tB37uvQHgubLMjW406blV 6O3uYmwKUSyklLy2RgJJS/gNR4zZ45DnySrm8wRS15eMlxs/OzoM5RcQlVOtQbb9C40F9hjZtL/ VPGSoLRmHAuCwBzjr6UuicbwdZq3XqHbagl8eHckKtTrYYFX5FbtFS8vCfvsjve3mG4KZO+LtT0 Zo0b5P2nac= X-Received: by 2002:a05:690c:e361:b0:7d3:cf30:efbf with SMTP id 00721157ae682-7de466e551emr6975567b3.17.1780019546080; Thu, 28 May 2026 18:52:26 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:25 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v8 02/10] landlock: Use landlock_walk_path_up() in is_access_to_paths_allowed() Date: Thu, 28 May 2026 21:52:01 -0400 Message-ID: <20260529015210.500291-3-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Replace the open-coded path-walk loop with the new landlock_walk_path_up() helper. This removes the backward goto and keeps the traversal logic in a single place. No functional change intended. Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Reworded commit message. * Reordered switch arms so the LANDLOCK_WALK_CONTINUE fast path comes first, and moved the per-case explanatory comments inside the case bodies. No functional change. security/landlock/fs.c | 55 ++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 8e75583c3ca7..8fb0aa59e180 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -921,46 +921,27 @@ is_access_to_paths_allowed(const struct landlock_rule= set *const domain, if (allowed_parent1 && allowed_parent2) break; =20 -jump_up: - if (walker_path.dentry =3D=3D walker_path.mnt->mnt_root) { - if (follow_up(&walker_path)) { - /* Ignores hidden mount points. */ - goto jump_up; - } else { - /* - * Stops at the real root. Denies access - * because not all layers have granted access. - */ - break; - } - } - - if (unlikely(IS_ROOT(walker_path.dentry))) { - if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) { - /* - * Stops and allows access when reaching disconnected root - * directories that are part of internal filesystems (e.g. nsfs, - * which is reachable through /proc//ns/). - */ - allowed_parent1 =3D true; - allowed_parent2 =3D true; - break; - } - + switch (landlock_walk_path_up(&walker_path)) { + case LANDLOCK_WALK_CONTINUE: + continue; + case LANDLOCK_WALK_INTERNAL: /* - * We reached a disconnected root directory from a bind mount. - * Let's continue the walk with the mount point we missed. + * Stops and allows access when reaching disconnected + * root directories that are part of internal + * filesystems (e.g. nsfs, which is reachable through + * /proc//ns/). */ - dput(walker_path.dentry); - walker_path.dentry =3D walker_path.mnt->mnt_root; - dget(walker_path.dentry); - } else { - struct dentry *const parent_dentry =3D - dget_parent(walker_path.dentry); - - dput(walker_path.dentry); - walker_path.dentry =3D parent_dentry; + allowed_parent1 =3D true; + allowed_parent2 =3D true; + break; + case LANDLOCK_WALK_STOP_REAL_ROOT: + /* + * Stops at the real root. Denies access because not + * all layers have granted access. + */ + break; } + break; } path_put(&walker_path); =20 --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f171.google.com (mail-yw1-f171.google.com [209.85.128.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D6B98364EB6 for ; Fri, 29 May 2026 01:52:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019552; cv=none; b=ktoTDQ29q923U2m8nmcwiZ+DOcMF/Os/jlZ2AWma5kdyp1Awegir+HYMjX0i971BNQSwcTpYFk4zqzmjXng1pEws/Tl9yeKGiSCkkvlBF3U4S63d5Ne9XxbHG3nyK8UKJHQ+NMYT+bvkzzuXnOW6A10A1n4cmdSE5MLRslonDMY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019552; c=relaxed/simple; bh=HcvlPGbYYcSSgeroOH8OD8abpGKj69jG2s+1OyCtyk4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=X3KwvjrVOhUKhJAFmHlNCYxz9JRi53dW+9Iqo7XIkmQNAfTmJV+Gp8uTIZ0D/Z8WMAd+wK1tbW/mjPJpCvaHwl81wQkV0T5B9zWnztRUKF2snFCbZHz2fUA8OO7NtP9mJuEXKeVetyGbOvWMQby6F4eOggXDo73TnCzj3wm8qwk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=S+ynRDnA; arc=none smtp.client-ip=209.85.128.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="S+ynRDnA" Received: by mail-yw1-f171.google.com with SMTP id 00721157ae682-7cfd0d8eb09so102287217b3.1 for ; Thu, 28 May 2026 18:52:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019549; x=1780624349; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=kk/ZE84jlVrlpz2dVoRSRY/S4Vc2qlm/+zWMZXovefE=; b=S+ynRDnAqLrT1MxbTWZTSatc6p3wmadezJQDlz0lu9HgH5G2i5LSBOTG3MeYgn5984 9xOIxwPbkfAIwUMQPsGpckng7SnUXvXesayN1ENVWFRm40wEbYEhOdIGnkcx9MD3yRG+ XJ/qaHdiusR5skZqobVN+wmv0DXBUBTHPpkcvhIvdR8/aavt4MOmlaEvYGm+aN0WpI3O M7bAKKub5cbIIwaiUz/mkFbdQ+SNw1Ynuhpk8nKBBwFgyzd7VoqhyJjythjysbIruFrw b/inNrqUTQAXgpkXVmaqJ6quU+tX5l9s/yuQsa5o7AVRzBUOYcqngXFEkj+evvnkyhQA tMTQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019549; x=1780624349; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=kk/ZE84jlVrlpz2dVoRSRY/S4Vc2qlm/+zWMZXovefE=; b=RGGAzPVn/R8nP8qwskH9JZlWrzGmQe08dx7O9thDsLUlTYArEiPOOqlJmg7nK9ekXA ZE/7UhV5osv6XPkBzOK5b4vaAnCX+0xoma7agJlXOpbYEYchbEQW4VLy6jFeYd6IbgdV VYweWW0jfIL6uhyBygQRSFzKCdk4/pxYMqZJUPHXkPM+/Z5g4B1IhjFGau45ibQ3iCZG KrGSsujwe3HoLWANDubGjJ8MRT7okvQAQgcCJACjzidZO35rSGIoAB8/I7VXxc5YDvsx PKbUiVU3Dd/yc5HJbOppokylKBOwsubvwHdD5uTZeTZRME4iEM2BzsFTlaWselRUm8Bx U7Tg== X-Gm-Message-State: AOJu0YwOvcGhs9p0VMUVfjMl7EWLtF/mKKMcdazxkC5LxZj5OlfXqIzV A+zBe7OpmcuiRYAnETYndpaTG6NC4zZ7TPcpcIJwGnYHYTLnupjK3TC3UgicRw== X-Gm-Gg: Acq92OEB6B3DVh4DTPfZ2UasV5uJRhoYIFiWnFJSvxEwf0J5WAEcDDBjuhIzXDg8N1p fG+XY+K4ZSfWVo3iJpXl3hFstmu4XY1t6kR2OCBHwmNIZ6zTvghsyzeyElbm1jjnpvH5mgsmreg 1BEuabKvWovZPFqAN30tTQ7Gn0XtZhWpN9TgYNxYfoT68zSuJArA9gRqltTX7wvkcK3K7qcOQCX +NlpaE6soh0BI7Ol/7Hyw7seoGeY1E5yj/IzyeXhEzw7REMRl+uyiQTOPnFtuqKjtGHjkU7eaNk Lqo2dOJ8UDdjdRWL964kFAqszm3Q7QtUYMFqHIsih5nn+AQF1xRt0H0qro2NACl1AFFIwaJkAT0 tHS2Nmz2g0NfwNKhhTTxvAOYYQtrctXbkWGCk+soTs2ZQ335UZC8VXIyXHaOrfD5nbHmUnSqJjE bjr43t6GdLmZLtFFp+cgqAWGkym9eCiJhSKnS5hPzx7uYXwNaKcEe1HcXAqmQMjTiBa1q/+s8zf X6riMM/Ccs= X-Received: by 2002:a05:690c:630d:b0:7c7:5111:5ee1 with SMTP id 00721157ae682-7de4a9c0854mr6994677b3.26.1780019548631; Thu, 28 May 2026 18:52:28 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:28 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v8 03/10] landlock: Use landlock_walk_path_up() in collect_domain_accesses() Date: Thu, 28 May 2026 21:52:02 -0400 Message-ID: <20260529015210.500291-4-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Replace the open-coded loop with landlock_walk_path_up() and change the function signature from (mnt_root, dir) to a single struct path. The caller's mount point and starting dentry are now both carried in @path, which keeps the traversal logic consistent with is_access_to_paths_allowed(). No functional change intended. Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Reworded commit message. * Changed collect_domain_accesses() to take a single struct path * instead of separate mnt_root/dir parameters, simplifying the interface and matching is_access_to_paths_allowed(). * Tightened the disconnected-directory stop condition to require !d_unhashed(walker_path.dentry) when comparing against the mount root, so disconnected bind-mount roots are not mistaken for the real mount root. security/landlock/fs.c | 82 ++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 8fb0aa59e180..6552351e0b9c 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -1032,48 +1032,51 @@ static access_mask_t maybe_remove(const struct dent= ry *const dentry) * collect_domain_accesses - Walk through a file path and collect accesses * * @domain: Domain to check against. - * @mnt_root: Last directory to check. - * @dir: Directory to start the walk from. + * @path: Path to start the walk from and whose mount root is the last + * directory to check. * @layer_masks_dom: Where to store the collected accesses. * - * This helper is useful to begin a path walk from the @dir directory to a - * @mnt_root directory used as a mount point. This mount point is the com= mon - * ancestor between the source and the destination of a renamed and linked - * file. While walking from @dir to @mnt_root, we record all the domain's - * allowed accesses in @layer_masks_dom. + * This helper is useful to begin a path walk from @path to the mount root + * directory used as a mount point. This mount point is the common ancest= or + * between the source and the destination of a renamed and linked file. W= hile + * walking from @path to that mount root, we record all the domain's allow= ed + * accesses in @layer_masks_dom. * - * Because of disconnected directories, this walk may not reach @mnt_dir. = In - * this case, the walk will continue to @mnt_dir after this call. + * Because of disconnected directories, this walk may not reach that mount + * root. In this case, the walk will continue to the mount root after this + * call. * * This is similar to is_access_to_paths_allowed() but much simpler becaus= e it * only handles walking on the same mount point and only checks one set of * accesses. * - * Return: True if all the domain access rights are allowed for @dir, fals= e if - * the walk reached @mnt_root. + * Return: True if all the domain access rights are allowed for @path, fal= se if + * the walk reached the mount root. */ -static bool collect_domain_accesses(const struct landlock_ruleset *const d= omain, - const struct dentry *const mnt_root, - struct dentry *dir, - struct layer_masks *layer_masks_dom) +static bool +collect_domain_accesses(const struct landlock_ruleset *const domain, + const struct path *const path, + struct layer_masks *layer_masks_dom) { bool ret =3D false; + struct path walker_path; =20 - if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom)) + if (WARN_ON_ONCE(!domain || !path || !path->dentry || !path->mnt || + !layer_masks_dom)) return true; - if (is_nouser_or_private(dir)) + if (is_nouser_or_private(path->dentry)) return true; =20 if (!landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, layer_masks_dom, LANDLOCK_KEY_INODE)) return true; =20 - dget(dir); + walker_path =3D *path; + path_get(&walker_path); while (true) { - struct dentry *parent_dentry; - /* Gets all layers allowing all domain accesses. */ - if (landlock_unmask_layers(find_rule(domain, dir), + if (landlock_unmask_layers(find_rule(domain, + walker_path.dentry), layer_masks_dom)) { /* * Stops when all handled accesses are allowed by at @@ -1084,17 +1087,19 @@ static bool collect_domain_accesses(const struct la= ndlock_ruleset *const domain, } =20 /* - * Stops at the mount point or the filesystem root for a disconnected - * directory. + * Stops at the mount point or the filesystem root for a + * disconnected directory. */ - if (dir =3D=3D mnt_root || unlikely(IS_ROOT(dir))) + if ((walker_path.dentry =3D=3D path->mnt->mnt_root && + walker_path.mnt =3D=3D path->mnt) || + unlikely(IS_ROOT(walker_path.dentry))) break; =20 - parent_dentry =3D dget_parent(dir); - dput(dir); - dir =3D parent_dentry; + if (WARN_ON_ONCE(landlock_walk_path_up(&walker_path) !=3D + LANDLOCK_WALK_CONTINUE)) + break; } - dput(dir); + path_put(&walker_path); return ret; } =20 @@ -1160,7 +1165,7 @@ static int current_check_refer_path(struct dentry *co= nst old_dentry, bool allow_parent1, allow_parent2; access_mask_t access_request_parent1, access_request_parent2; struct path mnt_dir; - struct dentry *old_parent; + struct path old_parent_path; struct layer_masks layer_masks_parent1 =3D {}, layer_masks_parent2 =3D {}; struct landlock_request request1 =3D {}, request2 =3D {}; =20 @@ -1214,18 +1219,19 @@ static int current_check_refer_path(struct dentry *= const old_dentry, /* * old_dentry may be the root of the common mount point and * !IS_ROOT(old_dentry) at the same time (e.g. with open_tree() and - * OPEN_TREE_CLONE). We do not need to call dget(old_parent) because - * we keep a reference to old_dentry. + * OPEN_TREE_CLONE). We do not need to call path_get(&old_parent_path) + * because we keep a reference to old_dentry. */ - old_parent =3D (old_dentry =3D=3D mnt_dir.dentry) ? old_dentry : - old_dentry->d_parent; + old_parent_path.mnt =3D mnt_dir.mnt; + old_parent_path.dentry =3D (old_dentry =3D=3D mnt_dir.dentry) ? + old_dentry : + old_dentry->d_parent; =20 /* new_dir->dentry is equal to new_dentry->d_parent */ - allow_parent1 =3D collect_domain_accesses(subject->domain, mnt_dir.dentry, - old_parent, + allow_parent1 =3D collect_domain_accesses(subject->domain, + &old_parent_path, &layer_masks_parent1); - allow_parent2 =3D collect_domain_accesses(subject->domain, mnt_dir.dentry, - new_dir->dentry, + allow_parent2 =3D collect_domain_accesses(subject->domain, new_dir, &layer_masks_parent2); if (allow_parent1 && allow_parent2) return 0; @@ -1244,7 +1250,7 @@ static int current_check_refer_path(struct dentry *co= nst old_dentry, return 0; =20 if (request1.access) { - request1.audit.u.path.dentry =3D old_parent; + request1.audit.u.path.dentry =3D old_parent_path.dentry; landlock_log_denial(subject, &request1); } if (request2.access) { --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f177.google.com (mail-yw1-f177.google.com [209.85.128.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BEC6C363C7F for ; Fri, 29 May 2026 01:52:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019554; cv=none; b=IM+X49g2sjJAZnVHHGvRipOGUf2Pc2vvP8Los5kYvDFNP2lKCEkvRg06lUl7TQIEfOEnh0Vw03IDzLAXVWzqYcxeTNqhZg9Zy+AHRiE8zH8S695einLqV60znv0gkvqbTnhrbP3XI0X8zgDwSuXTEGWla+ieGpZ4KAOehY2GSFY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019554; c=relaxed/simple; bh=FhoN1qEV4juRr7BI4kFjz/UOTAM5HIAT6zECoIPKxXA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=EeLzWL0aapGDGWBD5vXWs/OJ+SP7G5bXon4DQmQ2aXUsZkRVHfSjXgJZfAa1EAJ5w/fXsW8htdOvjo9k+GQJ4h4offWRUTGIedg6vgZgqdwb51ViRGgcRU0KPdgJcXsinffNvQTE7evJsMnkkahyfAtbXo5yMzN7wjxhr91GIXQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=rfWm+hfD; arc=none smtp.client-ip=209.85.128.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="rfWm+hfD" Received: by mail-yw1-f177.google.com with SMTP id 00721157ae682-7ca947f9b00so132000787b3.0 for ; Thu, 28 May 2026 18:52:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019551; x=1780624351; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=DBdvQt1aBRAc7GeMjYU8mb1pyck4wJw9l4x/B5fftoQ=; b=rfWm+hfDAFliuBx5j5P84o8gtZdJ7wh7KVClmsDELcbWbJDVHNnF8V6VXQN1XgtY+q 24UUtmQG5MCX/6OVEUKZhjgz+CpjR/IsRhSOAHIbCYQmNy/kVqvcVvKLHutOZwLjOblL f9NP8TB6rbYa5FFOCudesox8AvVF8WIF0ZwbuvOVZQxAlBp1ROGwG+kbtdFLPUrSASPW dMoyPOXPOoPJoowSLJeenkAIH2ARLHaBJMZnDc5VfwMRZPdSg69IxT/SbYwcvyjsBx9G WMeCQcP1VFY1yxVT8R38+LUbT1Ujd0AxrkXXje2f776ovG8H3EFk0k0NX7I5MsjEP/Gh /gfg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019551; x=1780624351; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=DBdvQt1aBRAc7GeMjYU8mb1pyck4wJw9l4x/B5fftoQ=; b=mMWKzdq91KXK7a3eCysqDXEXB/0mvBD9O0Vs32dAFtxRd5tMk7aK2fHjORjqzrzdFW miJwh3aKMlkgVDut6DxAaXJn6XtAkMG6L/Y+mDrIp2RcWk1KDgobJrACSCqV+VccICkY RsbBQhY+PDx+8LHHLBzaviKWNf9KTk+mzWPVVcAgLio++YCkjuGV6ug2XJQEr5dMQRJ1 V2IdIy6kYXvUOysRgFvi8V/DMPow+TY3GCw5cLNoodJrQ9mWvMkNf94nhgWUd6/CLLGh Clm3E00YjuPjr7kyHq+mYnI+ciHJypg2y2Lag1ImOmNgHsBZ//tOFyadqAlvSaaJEy2J rJTQ== X-Gm-Message-State: AOJu0Yy6cbojNXnzo9G1a4ptysEenEnB4t8gLuboXp0bkMKIU5IcIiGq i1VuwWXTf9SnmUvUfkWsN9Ja+uyiraHQNaxWXIm+vIvpPVIDGKKFp75q+b3YAg== X-Gm-Gg: Acq92OGWkpq/hUxrAKcHk7uLOMKo5+OOJeCpu7qnfhYlRzCZfjVHxdqTVMnIdmcMo+H swTJLnSXE5BuTU4USux5MyqIcMw+hb2nq38O7xy/n+5MB2huB91oFiMu/Ds4qYLDcHchmpeWO3Q wV0KA4hYuaGBKdCvO8wGmymVGk5dy6drHyxg9LO8bPzW8Ol4KmH9YJXAtz1P3xsAEQLr5wdFaxd 6bGvJS0Iz6pRoRn/yjyLT4/UVIfcKFpvEZTs5I5ipo4a/jnKdpvje8DETnZzQWlplXx5VU/LXy0 VISApx3qQNbDY8otGX6z1IAvQGRuZ/8rgOYBcNJcMahxHgm3X0rP224WaW4LVixZ3tpjlhHGE9y tr7sRx3harlEgvHhxvjlDY5vD919pE+QvUW5YC30IDEZko0ZAzuhDCVnhlgn3fxs/bV0v2Av4Tc oJi34KnUsCJvgURrqPi9IZPZfvChR1PcczTgRsQ34hq40oPnAgMGns2vEMtalLjH7DGrx2m7KF2 NVjqMS4aUA= X-Received: by 2002:a05:690c:4803:b0:7bd:577f:56bf with SMTP id 00721157ae682-7de489ca65emr6913457b3.33.1780019550714; Thu, 28 May 2026 18:52:30 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:30 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v8 04/10] landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT user API Date: Thu, 28 May 2026 21:52:03 -0400 Message-ID: <20260529015210.500291-5-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Wire up the new LANDLOCK_ADD_RULE_NO_INHERIT flag for sys_landlock_add_rule(). Define the constant in the UAPI header with its documentation, accept it from user space for %LANDLOCK_RULE_PATH_BENEATH only, and update the path-beneath useless- rule check so that an empty allowed_access is still accepted when a flag (quiet or no-inherit) is present. The flag has no enforcement effect yet; that is added in a subsequent patch. Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Renamed patch from "Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api" to "Add LANDLOCK_ADD_RULE_NO_INHERIT user API". * Reworded the UAPI documentation for LANDLOCK_ADD_RULE_NO_INHERIT in include/uapi/linux/landlock.h for clarity. * Centralized flag validation in sys_landlock_add_rule(): rejects unknown flags with a single ~(QUIET | NO_INHERIT) mask, and rejects NO_INHERIT on non-path-beneath rule types from the syscall entry point. * Removed the now-redundant LANDLOCK_ADD_RULE_NO_INHERIT check in add_rule_net_port(). * Documented the new EINVAL case for NO_INHERIT on unsupported rule types in the syscall kernel-doc. include/uapi/linux/landlock.h | 24 ++++++++++++++++++++++++ security/landlock/syscalls.c | 14 +++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 90a0752b61bf..d6de209ab961 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -124,10 +124,34 @@ struct landlock_ruleset_attr { * allowed_access in the passed in rule_attr. When this flag is * present, the caller is also allowed to pass in an empty * allowed_access. + * %LANDLOCK_ADD_RULE_NO_INHERIT + * Disable the inheritance of access rights and flags from parent obje= cts + * for the rule's object and its descendants. + * + * This flag currently applies only to filesystem rules. Passing it w= ith + * any other rule type returns ``-EINVAL``. + * + * By default, Landlock filesystem rules inherit allowed accesses from + * ancestor directories: rights granted on a parent directory also app= ly + * to its children. A rule marked with %LANDLOCK_ADD_RULE_NO_INHERIT + * stops this propagation at its object; only the accesses explicitly + * allowed by the rule apply. Descendants of that object continue to + * inherit from it normally, unless they too carry this flag. + * + * This flag also enforces parent-directory restrictions: rename, rmdi= r, + * link, and other operations that would change the immediate parent of + * the rule's object or any of its ancestors are denied up to the VFS + * root. This prevents sandboxed processes from manipulating the + * filesystem hierarchy to evade restrictions (e.g. via sandbox-restart + * attacks). + * + * Inheritance of rule flags (such as %LANDLOCK_ADD_RULE_QUIET) from + * ancestor directories is also blocked at the rule's object. */ =20 /* clang-format off */ #define LANDLOCK_ADD_RULE_QUIET (1U << 0) +#define LANDLOCK_ADD_RULE_NO_INHERIT (1U << 1) /* clang-format on */ =20 /** diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 08b6045d6926..04dacfdfc9f3 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -361,7 +361,7 @@ static int add_rule_path_beneath(struct landlock_rulese= t *const ruleset, /* * Informs about useless rule: empty allowed_access (i.e. deny rules) * are ignored in path walks. However, the rule is not useless if it - * is there to hold a quiet flag. + * carries a flag (quiet or no-inherit). */ if (!flags && !path_beneath_attr.allowed_access) return -ENOMSG; @@ -433,7 +433,7 @@ static int add_rule_net_port(struct landlock_ruleset *r= uleset, * @rule_type: Identify the structure type pointed to by @rule_attr: * %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT. * @rule_attr: Pointer to a rule (matching the @rule_type). - * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET. + * @flags: Bitmask of %LANDLOCK_ADD_RULE_* flags. * * This system call enables to define a new rule and add it to an existing * ruleset. @@ -451,6 +451,8 @@ static int add_rule_net_port(struct landlock_ruleset *r= uleset, * - %EINVAL: &landlock_net_port_attr.port is greater than 65535; * - %EINVAL: LANDLOCK_ADD_RULE_QUIET is passed but the ruleset has no * quiet access bits set for the corresponding rule type. + * - %EINVAL: LANDLOCK_ADD_RULE_NO_INHERIT is passed for a rule type + * that does not support it (e.g. %LANDLOCK_RULE_NET_PORT). * - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_acc= ess is * 0) and no flags; * - %EBADF: @ruleset_fd is not a file descriptor for the current thread, = or a @@ -472,7 +474,13 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_= fd, if (!is_initialized()) return -EOPNOTSUPP; =20 - if (flags && flags !=3D LANDLOCK_ADD_RULE_QUIET) + /* Rejects unknown flags. */ + if (flags & ~(LANDLOCK_ADD_RULE_QUIET | LANDLOCK_ADD_RULE_NO_INHERIT)) + return -EINVAL; + + /* LANDLOCK_ADD_RULE_NO_INHERIT only applies to path-beneath rules. */ + if ((flags & LANDLOCK_ADD_RULE_NO_INHERIT) && + rule_type !=3D LANDLOCK_RULE_PATH_BENEATH) return -EINVAL; =20 /* Gets and checks the ruleset. */ --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f176.google.com (mail-yw1-f176.google.com [209.85.128.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F37FE3644D1 for ; Fri, 29 May 2026 01:52:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019555; cv=none; b=uFZU638gMdEubWqZdHXKlG6oclO6PeHu/FnJo4m8n+vrtvXQOon4z3FISca5eN2iK4xeRkMTDOc0smzVOsD6Q8dmjz5nZHCTJCDPsXVHMNG39KLJPfOyEhNLEowe5oU4C4KEbahpd+jZ+jT6W5QUBlHKPg24mE5IEv9QCUMJtcg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019555; c=relaxed/simple; bh=V4qyqYkyEOcnTAI510w7NrX5J6c++/Ah8EHhTYHuTHM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=D5nxIrV83wubGqxddUKItB2RAEynOUw/mx9TP8ZIMrYcTYL3++gZDraE21El1h+ZiLpJLHjeaJjiCgbeK4g40rX50hev6tIx7mlhs4z8R7kat9DVAGAMLsmliMPZLejFbz+STJljy0+wvCCC3mUaWuyutsabxcAc6kOZipRZJcs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=nAIeoxBQ; arc=none smtp.client-ip=209.85.128.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="nAIeoxBQ" Received: by mail-yw1-f176.google.com with SMTP id 00721157ae682-7c52e49d978so140868047b3.2 for ; Thu, 28 May 2026 18:52:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019553; x=1780624353; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=dFCbqPU2dS/yUCY9uXq7gRsMM8ystmO/PODxvI3hCfQ=; b=nAIeoxBQqpaQXpx9Rgajl6gcSmKbOaqBI5FvKOmtjdPxJ7uUDD+DLzRz7nOuoyFfnN psx/VYVAyYeXrekgN6z+/pTSufCxE4F52FBJKDz8VueyNjs3MFZksavDCGgETjr9sWeT 3Gf/ai/97OWZiXtGownSh+6DHXfAL+3c0uXElI6ae0W4LKNLMjgoMEm9vO0nqZ2LdC+b 3OJ0Iz9ADtNP0Orh3/doS8WfzEpOTZ7p6pDlvjmGtnSPeMUhI3TLdfVFGl/gNoIF1zYj E6IDwmQ/l2bJICFTK0pAm7zwyRBRrVQ6ICWFNYrrAphS0eB32J2j4C+fT273QMKVF7iT AUgw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019553; x=1780624353; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=dFCbqPU2dS/yUCY9uXq7gRsMM8ystmO/PODxvI3hCfQ=; b=prc7/TfysB+KfWdOxXQZnkCBu3bRNIcg3FC2O5zilEZ36mAgNZOWbXl6spb+a9K9hR SZU6KOQsQG3XzmRbfGA63nivNN8Kr2Ig4LFUU161dPan0TbiTY7bIbsSIVxn8LvSQmyH 6ScffvxaMHSdo4QEDfMPqo6PpYpi3lo4SYkFE4REe+E9hsNuE9ZMkA6alsQXDaeV5g/z 5XfVP1Utg++yorlqESvq/aZ3ANyNfUSYvHisz0LUDBzl/Vp1BZkwoahqat+V+hfWYpog EnFzxHzODUSg1Ol9E7dll7TkpsnfTfO0ShEPvQw3k+AOhHSCnhAnhh9a/BeilZTp3gO9 A/ng== X-Gm-Message-State: AOJu0YxDgceRqq7/b7JvtGz+zHyrXXta5XMPffelsGLjmEltvtnC+Mt+ ow8ngV5ibDY9TB/t8tXR+XRl5OING6VP9Am3zIGHRE8YSq/n79/dCdXnUTc7gA== X-Gm-Gg: Acq92OF+ZwmmaDTfQ22mAy+YwgfY4pMmYwipD4gfRGlevUfy4OccEZUnYpVJcDFEFsh duyqTrXa15g7EWFAiCGIPSyUPFle+SupbBVo0WWSEwEJYsQLFBVJEqbwwJ79+ziVEGtkBRbsTzC Sc4K8lYDhaPdu8RuwQKJTIuV9xgGWnfHvnkPTCCpMh2UYdkBb65vs7dbYuPXrCBIJxaUt9RgiYH kanivooERB3u+No6scYmEIEFv1TiKYvalvvn3K3J1aodUquiJusPDhHFrYUaY3pys9JyYXqN+UJ cGt8e+u4e9x56T8pBAa7rwtG53ZVrMeffYpJ/uD4yX3HGC8NxsKF6+LLrBYTsGTe4zhTuvvV9WV ng+U25QE3XfuClazPMYsK1D3SXcFb65KiNAc6hv0xSIUQhkGTgchpwJKBtL8fjx084VHtWtWcwS gdFFb5aLnAgS+m9g/G3EB2Mcu5SJ+QEPY6KxG5FpsE92yo+70EofmW578xvsCQmEApNxO2DUAUe paSHMOgiAo= X-Received: by 2002:a05:690c:260f:b0:7dc:211:d718 with SMTP id 00721157ae682-7de4529219fmr8127397b3.4.1780019552984; Thu, 28 May 2026 18:52:32 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:32 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v8 05/10] landlock: Return inserted rule from landlock_insert_rule() Date: Thu, 28 May 2026 21:52:04 -0400 Message-ID: <20260529015210.500291-6-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Change insert_rule() and landlock_insert_rule() to return the inserted (or updated) struct landlock_rule pointer instead of an int errno. Errors are propagated via ERR_PTR(). This gives callers a handle on the resulting rule so a subsequent change can mutate per-layer flags on it (e.g. to mark ancestor rules created for no-inherit topology sealing). No functional change intended. Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Replaced the v7 "Move find_rule definition above landlock_append_fs_rule" patch with this new preparatory patch. Instead of moving find_rule(), make landlock_insert_rule() (and its static insert_rule() helper) return the inserted struct landlock_rule * via ERR_PTR(), so callers can directly tag flags on the resulting rule. Callers in net.c, merge_tree(), and inherit_tree() updated accordingly. No functional change. security/landlock/fs.c | 7 ++-- security/landlock/net.c | 8 +++-- security/landlock/ruleset.c | 68 ++++++++++++++++++------------------- security/landlock/ruleset.h | 7 ++-- 4 files changed, 48 insertions(+), 42 deletions(-) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 6552351e0b9c..ee7d9f5d7ee5 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -359,7 +359,8 @@ int landlock_append_fs_rule(struct landlock_ruleset *co= nst ruleset, const struct path *const path, access_mask_t access_rights, const int flags) { - int err; + int err =3D 0; + struct landlock_rule *rule; struct landlock_id id =3D { .type =3D LANDLOCK_KEY_INODE, }; @@ -378,7 +379,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *co= nst ruleset, if (IS_ERR(id.key.object)) return PTR_ERR(id.key.object); mutex_lock(&ruleset->lock); - err =3D landlock_insert_rule(ruleset, id, access_rights, flags); + rule =3D landlock_insert_rule(ruleset, id, access_rights, flags); + if (IS_ERR(rule)) + err =3D PTR_ERR(rule); mutex_unlock(&ruleset->lock); /* * No need to check for an error because landlock_insert_rule() diff --git a/security/landlock/net.c b/security/landlock/net.c index 60894cff973e..f08be4be275a 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -23,11 +23,11 @@ int landlock_append_net_rule(struct landlock_ruleset *c= onst ruleset, const u16 port, access_mask_t access_rights, const int flags) { - int err; const struct landlock_id id =3D { .key.data =3D (__force uintptr_t)htons(port), .type =3D LANDLOCK_KEY_NET_PORT, }; + struct landlock_rule *rule; =20 BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); =20 @@ -36,10 +36,12 @@ int landlock_append_net_rule(struct landlock_ruleset *c= onst ruleset, ~landlock_get_net_access_mask(ruleset, 0); =20 mutex_lock(&ruleset->lock); - err =3D landlock_insert_rule(ruleset, id, access_rights, flags); + rule =3D landlock_insert_rule(ruleset, id, access_rights, flags); mutex_unlock(&ruleset->lock); =20 - return err; + if (IS_ERR(rule)) + return PTR_ERR(rule); + return 0; } =20 static int current_check_access_socket(struct socket *const sock, diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index f01c3e14e55d..48397ab43a2d 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -203,12 +203,13 @@ static void build_check_ruleset(void) * added to @ruleset as new constraints, similarly to a boolean AND between * access rights. * - * Return: 0 on success, -errno on failure. + * Return: A pointer to the inserted or updated rule, or an ERR_PTR on fai= lure. */ -static int insert_rule(struct landlock_ruleset *const ruleset, - const struct landlock_id id, - const struct landlock_layer (*layers)[], - const size_t num_layers) +static struct landlock_rule * +insert_rule(struct landlock_ruleset *const ruleset, + const struct landlock_id id, + const struct landlock_layer (*layers)[], + const size_t num_layers) { struct rb_node **walker_node; struct rb_node *parent_node =3D NULL; @@ -218,14 +219,14 @@ static int insert_rule(struct landlock_ruleset *const= ruleset, might_sleep(); lockdep_assert_held(&ruleset->lock); if (WARN_ON_ONCE(!layers)) - return -ENOENT; + return ERR_PTR(-ENOENT); =20 if (is_object_pointer(id.type) && WARN_ON_ONCE(!id.key.object)) - return -ENOENT; + return ERR_PTR(-ENOENT); =20 root =3D get_root(ruleset, id.type); if (IS_ERR(root)) - return PTR_ERR(root); + return ERR_CAST(root); =20 walker_node =3D &root->rb_node; while (*walker_node) { @@ -243,7 +244,7 @@ static int insert_rule(struct landlock_ruleset *const r= uleset, =20 /* Only a single-level layer should match an existing rule. */ if (WARN_ON_ONCE(num_layers !=3D 1)) - return -EINVAL; + return ERR_PTR(-EINVAL); =20 /* If there is a matching rule, updates it. */ if ((*layers)[0].level =3D=3D 0) { @@ -252,16 +253,16 @@ static int insert_rule(struct landlock_ruleset *const= ruleset, * landlock_add_rule(2), i.e. @ruleset is not a domain. */ if (WARN_ON_ONCE(this->num_layers !=3D 1)) - return -EINVAL; + return ERR_PTR(-EINVAL); if (WARN_ON_ONCE(this->layers[0].level !=3D 0)) - return -EINVAL; + return ERR_PTR(-EINVAL); this->layers[0].access |=3D (*layers)[0].access; this->layers[0].flags.quiet |=3D (*layers)[0].flags.quiet; - return 0; + return this; } =20 if (WARN_ON_ONCE(this->layers[0].level =3D=3D 0)) - return -EINVAL; + return ERR_PTR(-EINVAL); =20 /* * Intersects access rights when it is a merge between a @@ -270,23 +271,23 @@ static int insert_rule(struct landlock_ruleset *const= ruleset, new_rule =3D create_rule(id, &this->layers, this->num_layers, &(*layers)[0]); if (IS_ERR(new_rule)) - return PTR_ERR(new_rule); + return ERR_CAST(new_rule); rb_replace_node(&this->node, &new_rule->node, root); free_rule(this, id.type); - return 0; + return new_rule; } =20 /* There is no match for @id. */ build_check_ruleset(); if (ruleset->num_rules >=3D LANDLOCK_MAX_NUM_RULES) - return -E2BIG; + return ERR_PTR(-E2BIG); new_rule =3D create_rule(id, layers, num_layers, NULL); if (IS_ERR(new_rule)) - return PTR_ERR(new_rule); + return ERR_CAST(new_rule); rb_link_node(&new_rule->node, parent_node, walker_node); rb_insert_color(&new_rule->node, root); ruleset->num_rules++; - return 0; + return new_rule; } =20 static void build_check_layer(void) @@ -305,9 +306,10 @@ static void build_check_layer(void) } =20 /* @ruleset must be locked by the caller. */ -int landlock_insert_rule(struct landlock_ruleset *const ruleset, - const struct landlock_id id, - const access_mask_t access, const int flags) +struct landlock_rule * +landlock_insert_rule(struct landlock_ruleset *const ruleset, + const struct landlock_id id, + const access_mask_t access, const int flags) { struct landlock_layer layers[] =3D { { .access =3D access, @@ -326,9 +328,8 @@ static int merge_tree(struct landlock_ruleset *const ds= t, struct landlock_ruleset *const src, const enum landlock_key_type key_type) { - struct landlock_rule *walker_rule, *next_rule; + struct landlock_rule *walker_rule, *next_rule, *rule; struct rb_root *src_root; - int err =3D 0; =20 might_sleep(); lockdep_assert_held(&dst->lock); @@ -358,11 +359,11 @@ static int merge_tree(struct landlock_ruleset *const = dst, layers[0].access =3D walker_rule->layers[0].access; layers[0].flags =3D walker_rule->layers[0].flags; =20 - err =3D insert_rule(dst, id, &layers, ARRAY_SIZE(layers)); - if (err) - return err; + rule =3D insert_rule(dst, id, &layers, ARRAY_SIZE(layers)); + if (IS_ERR(rule)) + return PTR_ERR(rule); } - return err; + return 0; } =20 static int merge_ruleset(struct landlock_ruleset *const dst, @@ -412,9 +413,8 @@ static int inherit_tree(struct landlock_ruleset *const = parent, struct landlock_ruleset *const child, const enum landlock_key_type key_type) { - struct landlock_rule *walker_rule, *next_rule; + struct landlock_rule *walker_rule, *next_rule, *rule; struct rb_root *parent_root; - int err =3D 0; =20 might_sleep(); lockdep_assert_held(&parent->lock); @@ -432,12 +432,12 @@ static int inherit_tree(struct landlock_ruleset *cons= t parent, .type =3D key_type, }; =20 - err =3D insert_rule(child, id, &walker_rule->layers, - walker_rule->num_layers); - if (err) - return err; + rule =3D insert_rule(child, id, &walker_rule->layers, + walker_rule->num_layers); + if (IS_ERR(rule)) + return PTR_ERR(rule); } - return err; + return 0; } =20 static int inherit_ruleset(struct landlock_ruleset *const parent, diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index ff163e5db5f0..5b7f554e8442 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -217,9 +217,10 @@ void landlock_put_ruleset_deferred(struct landlock_rul= eset *const ruleset); DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *, if (!IS_ERR_OR_NULL(_T)) landlock_put_ruleset(_T)) =20 -int landlock_insert_rule(struct landlock_ruleset *const ruleset, - const struct landlock_id id, - const access_mask_t access, const int flags); +struct landlock_rule * +landlock_insert_rule(struct landlock_ruleset *const ruleset, + const struct landlock_id id, + const access_mask_t access, const int flags); =20 struct landlock_ruleset * landlock_merge_ruleset(struct landlock_ruleset *const parent, --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f180.google.com (mail-yw1-f180.google.com [209.85.128.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2A389367B62 for ; Fri, 29 May 2026 01:52:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019559; cv=none; b=WF9bxLD0TuVkzNm23xv+1O7IqyO3Jh5kcChANmzO9HwHr9yxY0gYJnGhrcGSHCp3313uzmuMh2O667wmRp5P6jeW92p481Z43U5I24rRqRqCU31itNqg/ol5bC6g+R18ZKn5zGkNa2jHX4r+gjUkSzOqN2gncl00EYzsbmNyxyw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019559; c=relaxed/simple; bh=QugJtaPW1wkcSw1F+D0TQcpCXe9Neeo65mo6cxF+OQQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=c7oudRcpK0nnk7FTRG4Ii3P2jRh7V5q7TuFjE/1tkpSoRrDyRQ8a6nLmYSbS548qnl5iIYfKvN4+bqc7z0djz6H1MOekzKHLUQ3Yx5hKZEW7U6zTfM4SYqZKnIQ4Exd+1Viiz6kEs359Vx4zcx1BnSf1ANOigANY+IKA/B4jjvY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Z1xILmeM; arc=none smtp.client-ip=209.85.128.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Z1xILmeM" Received: by mail-yw1-f180.google.com with SMTP id 00721157ae682-7dd7818ac2aso6522387b3.1 for ; Thu, 28 May 2026 18:52:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019555; x=1780624355; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=d7CHCXr72km3LTx0xZQD8IgCm2NpcAMYh22DNkOLa9w=; b=Z1xILmeMVqFA+6YTX98ghCisQ02jYgDF4V16/vnE5OG308xp6J9L9a11B/KWJd2Vc6 zZjll6bCnwBs2fRQY2dLQI9YNuw67tANSLuY/r3z8cat2dFQXATUsV4832gEQYzyW3t7 cxsXkjoMKv/CWI2jXQbtXoddCy+LJGov0Ej5FWgFJhbNz+2FISay89qGAtjkEGBkiF1q 4AW6SeIq5tBJJBTUjIW7X1lkIpTnCv26G8LYfNnbKngMckowPY1uP27VM1sBF0Y5+YJN rRC4hzKzk5g2liXy4AV+/DBAKLA2Y2PTZtwtJz8Rvfm4wR4dIBfFR96lND3cxh67h3H/ oz6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019555; x=1780624355; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=d7CHCXr72km3LTx0xZQD8IgCm2NpcAMYh22DNkOLa9w=; b=sKFuzdi8pQuRVbpAmcoTzvhhW427Iyt8Pnbu62PHOoTHQtquR1wqtPq4Dl10M01gft sYK9iEgjw+XWkz1HlpEPkEBxttc3INsw5MhvvkyAfev8l5TMYhAVyyVODcviw9LunYqR s8z7857JrQd7STx9QWNFEUObkoj9p0Oha5Uf6XJm0Vu+qB5Kb4rz9b78iWOJNeqlpqoZ zqh2XUjc7ZHR9X844z5ibUb0bTs27n9FxSEmNsFi/A+6dvc7ViDuS7fpMfCl7+ordhmW nv4q9TPuyoSqMZT9CaC2sOGUV94G3Qmi5mTx+OkjR2ySajGyI4O+Kk9fdXlH9xOMXD27 P1+g== X-Gm-Message-State: AOJu0Yx+hRv1MkbQKm/OMNn/L05c1g+0H+lFkOjbRdTOWxQkUOgdyNz6 qeLevnCyM+G/TqIcdeHhzvki5ajvCDN/ZdHtTsPwrK9foISLyHNlMllU9FD4eA== X-Gm-Gg: Acq92OGkFEcD5TXBRW5hMf9NWuO0OFFrruRwgIKK36J2Wc1+E6WysI6fdZYGIYCsr1Q SQZyE5THnRGzejCug0Ar6E3OUyZKkYzJRUhJMDna6HdDQRYOvrRwsO5H/xQEgGH0zy1vz8A7iX9 c8PIS3VkROugTCb+a6T9SLN6/tsiNyK1svY11ifPBKz6boP7Cm+h8JmWgWcznXD82139TeWxyZr 68rW8JR8WuheWPaWmRkWTYAFXSUsyi5GCZrml90Sj+4iwImy4yHkWMtNqN/xkqGX9sjiVtW+gQB 5ElZR0T3iyztcMoR7ej7ciwbv0Gpth5w7m/Uzph0lG6ns8PE71diMd6ZwtD3JuGQzj0Ok6JJPmz MH+y2zsZLj6yD/2Jr17T27dIScfw3V3AVu3QdHFToUqkyXtKym9B5zSaMWnHLk3y1tdUfwCXYVM E/t2NaDi/gDkIUufFRoo7hyrx8G1Rn5COhLUAD12HXJEGqjHloUw/9whHLoIihdt0/rbBB2Su04 fgx6M31imQ= X-Received: by 2002:a05:690c:22c6:b0:7db:9594:5edc with SMTP id 00721157ae682-7de47e26d33mr7766637b3.44.1780019555111; Thu, 28 May 2026 18:52:35 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:34 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v8 06/10] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT Date: Thu, 28 May 2026 21:52:05 -0400 Message-ID: <20260529015210.500291-7-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Make %LANDLOCK_ADD_RULE_NO_INHERIT actually enforce its semantics: - Tag the new rule's layer with @no_inherit and @has_no_inherit_descendant so landlock_unmask_layers() stops walking up the hierarchy once it has been seen, and so the rule's own object is sealed against topology changes. - Walk from the rule's path up to the VFS root in landlock_append_fs_rule(), inserting a zero-access rule on each ancestor with @has_no_inherit_descendant set, so topology changes (rename, rmdir, link, ...) on any ancestor are denied too. - Add deny_no_inherit_topology_change(), called from current_check_refer_path(), hook_path_unlink() and hook_path_rmdir() to enforce the seal at the LSM hook layer. Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Reworded commit message to describe the three pieces of the implementation (per-layer tagging, ancestor walk, LSM hook enforcement). * Reworked ancestor sealing in landlock_append_fs_rule(): use the new landlock_insert_rule() return value to tag flags directly on the inserted ancestor rule, removing the prior find_rule() round trip after insertion. * Moved no_inherit / has_no_inherit_descendant tracking from a separate collected_rule_flags struct into the existing layer_mask struct as a single per-layer 'no_inherit' bit. landlock_unmask_layers() now skips layers whose mask already has no_inherit set, and landlock_init_layer_masks() clears the new bit. The 'has_no_inherit_descendant' rule-layer flag is auto-set on the rule's own object when LANDLOCK_ADD_RULE_NO_INHERIT is passed, sealing it against topology changes without a separate blank-rule insertion. * Simplified deny_no_inherit_topology_change(): dropped the override_layers accumulator (it was always 0 in practice) and now just OR-collects sealed layers from no_inherit / has_no_inherit_descendant. * Updated kerneldoc comments on the new layer flags. security/landlock/access.h | 4 ++ security/landlock/fs.c | 116 +++++++++++++++++++++++++++++++++++- security/landlock/ruleset.c | 30 +++++++++- security/landlock/ruleset.h | 13 ++++ 4 files changed, 159 insertions(+), 4 deletions(-) diff --git a/security/landlock/access.h b/security/landlock/access.h index 61a17b568652..ab5c1e0bc25d 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -71,12 +71,16 @@ static_assert(sizeof(typeof_member(union access_masks_a= ll, masks)) =3D=3D * * @quiet is used to store whether we have encountered a rule with the * quiet flag for this layer, which will be used to control audit logging. + * + * @no_inherit is used to mark this layer as having a no_inherit rule, so + * that ancestor rules in the same layer do not contribute access rights. */ struct layer_mask { access_mask_t access:LANDLOCK_NUM_ACCESS_MAX; #ifdef CONFIG_AUDIT bool quiet:1; #endif /* CONFIG_AUDIT */ + bool no_inherit:1; }; =20 /* diff --git a/security/landlock/fs.c b/security/landlock/fs.c index ee7d9f5d7ee5..3aa7d898efe1 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -364,6 +364,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *co= nst ruleset, struct landlock_id id =3D { .type =3D LANDLOCK_KEY_INODE, }; + struct path walker =3D *path; =20 /* Files only get access rights that make sense. */ if (!d_is_dir(path->dentry) && @@ -378,10 +379,47 @@ int landlock_append_fs_rule(struct landlock_ruleset *= const ruleset, id.key.object =3D get_inode_object(d_backing_inode(path->dentry)); if (IS_ERR(id.key.object)) return PTR_ERR(id.key.object); + mutex_lock(&ruleset->lock); rule =3D landlock_insert_rule(ruleset, id, access_rights, flags); - if (IS_ERR(rule)) + if (IS_ERR(rule)) { err =3D PTR_ERR(rule); + goto out_unlock; + } + if (!(flags & LANDLOCK_ADD_RULE_NO_INHERIT)) + goto out_unlock; + + /* + * Seal each ancestor up to the VFS root with a no-access rule + * tagged @has_no_inherit_descendant so that topology-changing + * operations (rename, rmdir, link, ...) on them are denied. + */ + path_get(&walker); + while (landlock_walk_path_up(&walker) !=3D LANDLOCK_WALK_STOP_REAL_ROOT) { + struct landlock_rule *ancestor_rule; + struct inode *const ancestor_inode =3D + d_backing_inode(walker.dentry); + struct landlock_id ancestor_id =3D { + .type =3D LANDLOCK_KEY_INODE, + .key.object =3D get_inode_object(ancestor_inode), + }; + + if (IS_ERR(ancestor_id.key.object)) { + err =3D PTR_ERR(ancestor_id.key.object); + break; + } + ancestor_rule =3D landlock_insert_rule(ruleset, ancestor_id, 0, + 0); + landlock_put_object(ancestor_id.key.object); + if (IS_ERR(ancestor_rule)) { + err =3D PTR_ERR(ancestor_rule); + break; + } + ancestor_rule->layers[0].flags.has_no_inherit_descendant =3D true; + } + path_put(&walker); + +out_unlock: mutex_unlock(&ruleset->lock); /* * No need to check for an error because landlock_insert_rule() @@ -1106,6 +1144,54 @@ collect_domain_accesses(const struct landlock_rulese= t *const domain, return ret; } =20 +/** + * deny_no_inherit_topology_change - Deny topology changes on sealed paths + * @subject: Subject performing the operation. + * @dentry: Target of the topology modification. + * + * Returns -EACCES (and emits an audit record) if any of the subject's + * domain layers seal @dentry against topology changes: either @dentry + * itself has a %LANDLOCK_ADD_RULE_NO_INHERIT rule, or one of its + * descendants does (recorded via @has_no_inherit_descendant on the + * dentry's rule). + * + * Returns 0 otherwise. + */ +static int +deny_no_inherit_topology_change(const struct landlock_cred_security *subje= ct, + struct dentry *const dentry) +{ + unsigned long sealed_layers =3D 0; + const struct landlock_rule *rule; + + if (WARN_ON_ONCE(!subject || !dentry || d_is_negative(dentry))) + return 0; + + rule =3D find_rule(subject->domain, dentry); + if (!rule) + return 0; + + for (size_t i =3D 0; i < rule->num_layers; i++) { + const struct landlock_layer *const layer =3D &rule->layers[i]; + + if (layer->flags.no_inherit || + layer->flags.has_no_inherit_descendant) + sealed_layers |=3D BIT(layer->level - 1); + } + if (!sealed_layers) + return 0; + + landlock_log_denial(subject, &(struct landlock_request) { + .type =3D LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, + .audit =3D { + .type =3D LSM_AUDIT_DATA_DENTRY, + .u.dentry =3D dentry, + }, + .layer_plus_one =3D __ffs(sealed_layers) + 1, + }); + return -EACCES; +} + /** * current_check_refer_path - Check if a rename or link action is allowed * @@ -1188,6 +1274,16 @@ static int current_check_refer_path(struct dentry *c= onst old_dentry, access_request_parent2 =3D get_mode_access(d_backing_inode(old_dentry)->i_mode); if (removable) { + int err =3D deny_no_inherit_topology_change(subject, old_dentry); + + if (err) + return err; + if (exchange) { + err =3D deny_no_inherit_topology_change(subject, + new_dentry); + if (err) + return err; + } access_request_parent1 |=3D maybe_remove(old_dentry); access_request_parent2 |=3D maybe_remove(new_dentry); } @@ -1579,12 +1675,30 @@ static int hook_path_symlink(const struct path *con= st dir, static int hook_path_unlink(const struct path *const dir, struct dentry *const dentry) { + const struct landlock_cred_security *const subject =3D + landlock_get_applicable_subject(current_cred(), any_fs, NULL); + + if (subject) { + int err =3D deny_no_inherit_topology_change(subject, dentry); + + if (err) + return err; + } return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE); } =20 static int hook_path_rmdir(const struct path *const dir, struct dentry *const dentry) { + const struct landlock_cred_security *const subject =3D + landlock_get_applicable_subject(current_cred(), any_fs, NULL); + + if (subject) { + int err =3D deny_no_inherit_topology_change(subject, dentry); + + if (err) + return err; + } return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); } =20 diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 48397ab43a2d..c78e2b2d73ff 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -258,6 +258,10 @@ insert_rule(struct landlock_ruleset *const ruleset, return ERR_PTR(-EINVAL); this->layers[0].access |=3D (*layers)[0].access; this->layers[0].flags.quiet |=3D (*layers)[0].flags.quiet; + this->layers[0].flags.no_inherit |=3D + (*layers)[0].flags.no_inherit; + this->layers[0].flags.has_no_inherit_descendant |=3D + (*layers)[0].flags.has_no_inherit_descendant; return this; } =20 @@ -311,12 +315,20 @@ landlock_insert_rule(struct landlock_ruleset *const r= uleset, const struct landlock_id id, const access_mask_t access, const int flags) { + const bool no_inherit =3D !!(flags & LANDLOCK_ADD_RULE_NO_INHERIT); struct landlock_layer layers[] =3D { { .access =3D access, /* When @level is zero, insert_rule() extends @ruleset. */ .level =3D 0, .flags =3D { .quiet =3D !!(flags & LANDLOCK_ADD_RULE_QUIET), + .no_inherit =3D no_inherit, + /* + * The rule's own object is also sealed against + * topology changes, so mark it as if it had a + * no-inherit descendant. + */ + .has_no_inherit_descendant =3D no_inherit, }, } }; =20 @@ -657,15 +669,25 @@ bool landlock_unmask_layers(const struct landlock_rul= e *const rule, */ for (size_t i =3D 0; i < rule->num_layers; i++) { const struct landlock_layer *const layer =3D &rule->layers[i]; + struct layer_mask *const layer_mask =3D + &masks->layers[layer->level - 1]; + + /* + * Skip layers that already have no_inherit set: these layers + * should not inherit access rights from ancestor directories. + */ + if (layer_mask->no_inherit) + continue; =20 /* Clear the bits where the layer in the rule grants access. */ - masks->layers[layer->level - 1].access &=3D ~layer->access; + layer_mask->access &=3D ~layer->access; =20 #ifdef CONFIG_AUDIT - /* Collect rule flags for each layer. */ if (layer->flags.quiet) - masks->layers[layer->level - 1].quiet =3D true; + layer_mask->quiet =3D true; #endif /* CONFIG_AUDIT */ + if (layer->flags.no_inherit) + layer_mask->no_inherit =3D true; } =20 for (size_t i =3D 0; i < ARRAY_SIZE(masks->layers); i++) { @@ -731,6 +753,7 @@ landlock_init_layer_masks(const struct landlock_ruleset= *const domain, #ifdef CONFIG_AUDIT masks->layers[i].quiet =3D false; #endif /* CONFIG_AUDIT */ + masks->layers[i].no_inherit =3D false; } for (size_t i =3D domain->num_layers; i < ARRAY_SIZE(masks->layers); i++) { @@ -738,6 +761,7 @@ landlock_init_layer_masks(const struct landlock_ruleset= *const domain, #ifdef CONFIG_AUDIT masks->layers[i].quiet =3D false; #endif /* CONFIG_AUDIT */ + masks->layers[i].no_inherit =3D false; } =20 return handled_accesses; diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 5b7f554e8442..249a736248db 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -40,6 +40,19 @@ struct landlock_layer { * down the file hierarchy. */ bool quiet:1; + /** + * @no_inherit: Prevents this rule from inheriting access rights + * from ancestor inodes. Only used for filesystem rules; set + * via %LANDLOCK_ADD_RULE_NO_INHERIT. + */ + bool no_inherit:1; + /** + * @has_no_inherit_descendant: Marker used to deny topology + * changes on the rule's object: either the object itself has + * a no-inherit rule, or a descendant does. Only used for + * filesystem rules; set by Landlock, never by user space. + */ + bool has_no_inherit_descendant:1; } flags; /** * @access: Bitfield of allowed actions on the kernel object. They are --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f178.google.com (mail-yw1-f178.google.com [209.85.128.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1C7C1367B8B for ; Fri, 29 May 2026 01:52:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019559; cv=none; b=DchRyBsURYnzQMJuARQNNGq4R8I40DAzNSyxTZoJL4WGOyklDhGwjueM3gnycIIH73s07bPGeaBlkojCkvucr5o8PMLG93PZPHw7ehe977iJofUC/1X8uPlDUJwd0l442PGR6N8cdVOEx141dlDpz4KedyBJ+xDUYokAs5xVUx0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019559; c=relaxed/simple; bh=trxUybHguCoxohnEyn58jFksS/yEq5TOeiwsKGYCY7Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HE35qQPTMiXhbGfUUhJxMadLVaCggERYOb5EBN2uk7g9FZff28j5znCtQHy9jIkhyJDhKyzCz1j3g2znPf7tmp9uiRPhdSsm/Jj5yFvQL8gRZEHHrodut5u2LL2YeIizHyE192n6mUhpK9XqlxH0ESTwIfCBxirHbcm/pueofbY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=aUbnCoZS; arc=none smtp.client-ip=209.85.128.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="aUbnCoZS" Received: by mail-yw1-f178.google.com with SMTP id 00721157ae682-7cb345cb5bfso114277067b3.0 for ; Thu, 28 May 2026 18:52:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019557; x=1780624357; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=weKJSMUU3m3+uwtuJiUMz+guBFkag//BiZTgAufcbA0=; b=aUbnCoZSirKaIyvQ4P8dEJJuEPuKrBI2hAt0EJ0OuIzVxE7vC5XgPKCaZ+xCPgBHlk /YQtG82eTjzlHIk8ZluXWwqsOLa18Ih9unMgw8kIoWwrkMZZw1UDM7WeOU39pZ4sJfot ZM4LEoT4PbA7vRNX8TwpFPfCme3m/fw619BsICEcN969nowY0QMMSzzE/T9QjvE4ddk1 jSGrm3HdMd7idDnhQa3/abl3+9rEIdtHKAhpJTCZwzVWKHG8AE3UDWaAm6TOjAgC4YqT mAzf8pcnC2O8m7BLxGq7KogjXgqh0Xo45jg7kZIrkmH7C3mWuHCO9z7M2K54sq+osqc3 lucA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019557; x=1780624357; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=weKJSMUU3m3+uwtuJiUMz+guBFkag//BiZTgAufcbA0=; b=EmRbfjfMq/BDzHXzQ+JRO4R5V4nlHeHpGSzruFZ/WKiPFFxuqFTxVjMcCJGoZeoaI0 p4fvZa3AhXqLboaeNB045sCoKM4jToj/+3O0x4Y5XnBa710x7x7ys1Bul2Uo+uvHaplE R5+0QB4KbUo/oPW6o6H/j+hWZJkpw7I6rB5nPM1c6xoO2KVnTytHDcokMTQ32R8KDbkY i7eBEymm+eAA4ylaFG5C/I1PGbKrC6Y8I9+5l6kwps0js/i2Du7Q1yLTEeMIus4UxMgC bGfrCqopX5x2XUJ5boemwUmL0zindQ1ck8n5gQFXmOxk9kh3f6codInxUV/PhCOJqBVH NdYA== X-Gm-Message-State: AOJu0Yx9QA0Kdh7QqddnPnaPpdbH9s3yby5jRg6nOJ7Ltoyj50EOxTNR kvCbaOete6cvjotj67+2dD0K9zkCcniNI1clsOzIl/IMa7203cXfMFML X-Gm-Gg: Acq92OEjUc7gaAnPE9pCIGQGzIG94NoVVkJ6aZBVxYOEGTyjOnbJKEY2H/XYUOukUPc Brv73gxYq38QCtrsxnXx2sEEF2KaiyuHI3enzenMLf9mfvS8WBC7CJllkwrBR1/lLD4iTw1hkD6 36puaeXl1m0POwwj2wUtSODtawDMi6/iWjm1sEyQdRD0E9b9JYUQ5jARi8s6Lx7MWlEmL+IUXbq 99fyM3+510cY2EDPnZ30FQJsWDK84PMLxH3fPgujAQMXeU1usmvNTMYq8D46J6pf4A6KB7906/Z xapoMXVHtIig3CnVZfqjrc8h6V3QPtQPy8dL4aAPj+d0+lS03taZzdP0h/32MDwLb5uHexihEPl LkOpX7WF73P7mau4rH8zrygaeJswcQyI42MuMea7/SRlLYah8fyYHowKqUcCDVeJrykf1g6PyIC f1bpDTmJR0koAIU9wWAhgNNAlY2KlPZTyeR4Cjo20rnDCUSTHeLYAZ6pCXThq3xfp3VAos1N/du YXOk+fjeHc= X-Received: by 2002:a05:690c:e3c8:b0:7dc:d9cd:1770 with SMTP id 00721157ae682-7de47474a55mr7395727b3.22.1780019557221; Thu, 28 May 2026 18:52:37 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:37 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v8 07/10] landlock: Add documentation for LANDLOCK_ADD_RULE_NO_INHERIT Date: Thu, 28 May 2026 21:52:06 -0400 Message-ID: <20260529015210.500291-8-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds documentation of the flag to the userspace api, describing the functionality of the flag and parent directory protections. Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Minor wording polish in the new 'Filesystem inheritance suppression' documentation section; no semantic change. Documentation/userspace-api/landlock.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/users= pace-api/landlock.rst index 138d504cb498..ae3136461b18 100644 --- a/Documentation/userspace-api/landlock.rst +++ b/Documentation/userspace-api/landlock.rst @@ -733,6 +733,24 @@ struct landlock_ruleset_attr. It is also now possible= to suppress audit logs for scope accesses via the ``quiet_scoped`` field of struct landlock_ruleset_attr. =20 +Filesystem inheritance suppression (ABI < 10) +--------------------------------------------- + +Starting with the Landlock ABI version 10, it is possible to prevent a +directory or file from inheriting its parent's access grants by using the +``LANDLOCK_ADD_RULE_NO_INHERIT`` flag passed to sys_landlock_add_rule(). +This is useful for policies where a parent directory needs broader access +than its children. + +To mitigate sandbox-restart attacks, the tagged inode and all of its +ancestors up to the VFS root cannot be removed, renamed, reparented, or +linked into or out of other directories. + +Inheritance of access grants from descendants of an inode tagged with +``LANDLOCK_ADD_RULE_NO_INHERIT`` is unaffected: such descendants continue +to inherit from the tagged inode normally, unless they also carry this +flag. + .. _kernel_support: =20 Kernel support --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f176.google.com (mail-yw1-f176.google.com [209.85.128.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CDD33367F5F for ; Fri, 29 May 2026 01:52:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019562; cv=none; b=Ap6sp4S+EtbQsDOOjVs5P7E15kPt2pQuzaMWdkByCTk+7UvTps5r6R0SajE+R8gEsPgnbTR2JrgSs8SSJxm36iw78ABt+sJDKGXoTN2QgY0mcuRjVVdGnH+qU43/vR7DkxBcF9Lt3OgTOxGy/dkueqyAtYBRyywP+DzKN3TO0dM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019562; c=relaxed/simple; bh=76kY0d60VUJ26O5KNgAsZo8z7YSKsQZJuHdM+J8sPOI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mFoich+RgFwyNFJguhLpdsBkuhT6Urj10+TvVWkk/zNlR8ELO6lYZuSIHUEvcX2j8oLAfOna5kRl/5IOjZ9g5hVM98nEh8e6fqdd8JdULklGIFY8GG4RE/hUYgBp+5tfHliD3q2ozfOLJd6pB6M0dEXbG8AkobdX1SEi9iArGe4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=n9D76WVO; arc=none smtp.client-ip=209.85.128.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="n9D76WVO" Received: by mail-yw1-f176.google.com with SMTP id 00721157ae682-7dd5a8dc8a2so4928757b3.0 for ; Thu, 28 May 2026 18:52:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019560; x=1780624360; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qqYnzo1LtHIFPy2ScHtDhYLy8CZ9V4kJ5TdrOpsVges=; b=n9D76WVO3A0lo5tYhoPojx964qgaALAtWIgyRf68wEW21YwGVACXH0CtgtxW95WAeO B1iv5KEyTipADjk0IonAMCam9rXm69b3W02+X7+rGzqwbnCNsb0TEqpGRllXZP3y6qV/ Zefwifw7WZ1+FDkHNXDadJxU6uEp8FQf/+hWHeIQbNxOrf9StYuRkec70W17OWvWUovl IzZ7tu8K7ui483aZRIn8Gju2rb83ro9Tbpi4vC5srEP6QdXBz9SY/0NYC7M6SKApOoxO 2f7c+UqY/rU0mbstpvdBtEmIJejxzvnyRT/NZF1HcbK/n9wkgL6DX+ELXjP6t3fqxnys tSLw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019560; x=1780624360; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=qqYnzo1LtHIFPy2ScHtDhYLy8CZ9V4kJ5TdrOpsVges=; b=bgs3mVWE8t5qN0rquo1zUPH93ediJdui6TR6/2+n49BKJK8zw3PaJgqes96ZCf4VdP /SHqHdzxds8M+ndQku6ugqjSVecOLoIa4us6ISsfBBdJQwwbGfLIwKmRZw0Yn6eQwHvQ 99Sm0UQlJhFy24gd5xS4x9Rm9uBQGH8Y6SzJ6/8yyZkLAGhaJ/IgBAkbV3epQrV8R5Ck EpIy+gGdVCoRqBVdpBaZmQ4vZejIXsHBMX9ef6CW03hMtMJxF8jc0d9HO58HCHZfrai5 te+i88U6p9wDh41heyuM/M6GWCfvQDjE4YdAjHjOm+pkGYXKbUW9VgCIr+7ogbeagfcX 4ivw== X-Gm-Message-State: AOJu0YyoiBectPp2Vt+QS1UdEexOrCaS3FP6w4f3iEbgePr/Vmrh09a/ y2DZcM6cjwJ4viaRdk2ElrAQB/Om9GcMWJYoCO4HGO014NMKrtocE2Wt X-Gm-Gg: Acq92OFIhY3oDw0BVYC4HOJYQMcYw3LD7k1CSci3TfxUhxDk8RjRFkqBIeOOqk7w+eH 0PXWixjpY7qAUsgsI8hkAS1gQEp20vI32pQShbmrtCadLdxN2T+meN8OMtWURRgQZ+EukIadkMN IEQ8nIy1bSPvS0cgs+GG5h0Ap1bEdGqXtwIL/5PnmHn1Mimbvr15ITk95LmCd5qwRLTMpj8xfgX Y7UpPyKJtixL/RSCGskXvCyPWqesTcVMLg1zJWCc1Thga+QUJYT2U0HS3VDv5LVo/O9xLWwst8M bWege4Rl4/8YEB6dPbt+qAlKalZ8HPpClF/oL4kfQzThD6Lj7R8U7gKLWHBKgIOeZ7lOeS6lXfA y54AodAKQX6Z8bQT+gP9MFLcSPvgRz0cRH6P98q1/t2J1fvg1RCswXII2+sbX6tjRhLMxLQV7fP FaNMThaX98CTSrplj4ZEWxxOaXdLtnpAeIM8EYWhC70x+Fm4k3L4hdbnR6yxekf5g+K7hb3XmZT kyzjZrw114= X-Received: by 2002:a05:690c:360e:b0:7d0:79f:338f with SMTP id 00721157ae682-7de46ea5662mr6709957b3.33.1780019559909; Thu, 28 May 2026 18:52:39 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:39 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess , Tingmao Wang Subject: [PATCH v8 08/10] samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to landlock-sandboxer Date: Thu, 28 May 2026 21:52:07 -0400 Message-ID: <20260529015210.500291-9-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a new LL_FS_NO_INHERIT environment variable to the sandboxer. Paths listed in it are added with the LANDLOCK_ADD_RULE_NO_INHERIT flag, demonstrating how to set up a parent directory with broader access than its children. The flag is silently skipped on kernels older than ABI 10. Cc: Tingmao Wang Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Reworded commit message. * Updated the ABI fallthrough comment to mention 'quiet or no_inherit flags'. No code change beyond the comment update. samples/landlock/sandboxer.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index 74ee53afed6a..b126ffd7cd4f 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -60,6 +60,7 @@ static inline int landlock_restrict_self(const int rulese= t_fd, #define ENV_FS_RW_NAME "LL_FS_RW" #define ENV_FS_QUIET_NAME "LL_FS_QUIET" #define ENV_FS_QUIET_ACCESS_NAME "LL_FS_QUIET_ACCESS" +#define ENV_FS_NO_INHERIT_NAME "LL_FS_NO_INHERIT" #define ENV_TCP_BIND_NAME "LL_TCP_BIND" #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" #define ENV_NET_QUIET_NAME "LL_NET_QUIET" @@ -395,6 +396,7 @@ static const char help[] =3D "but to test audit we can set " ENV_FORCE_LOG_NAME "=3D1\n" ENV_FS_QUIET_NAME " and " ENV_NET_QUIET_NAME ", both optional, can then b= e used " "to make access to some denied paths or network ports not trigger audit l= ogging.\n" + ENV_FS_NO_INHERIT_NAME " can be used to suppress access right propagation= (ABI >=3D 10).\n" ENV_FS_QUIET_ACCESS_NAME " and " ENV_NET_QUIET_ACCESS_NAME " can be used = to specify " "which accesses should be quieted (defaults to all):\n" "* " ENV_FS_QUIET_ACCESS_NAME ": file system accesses to quiet\n" @@ -447,6 +449,7 @@ int main(const int argc, char *const argv[], char *cons= t *const envp) }; =20 bool quiet_supported =3D true; + bool no_inherit_supported =3D true; int supported_restrict_flags =3D LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON; int set_restrict_flags =3D 0; =20 @@ -538,8 +541,9 @@ int main(const int argc, char *const argv[], char *cons= t *const envp) ~(LANDLOCK_ACCESS_NET_BIND_UDP | LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP); __attribute__((fallthrough)); - /* Don't add quiet flags for ABI < 10 later on. */ + /* Don't add quiet or no_inherit flags for ABI < 10 later on. */ quiet_supported =3D false; + no_inherit_supported =3D false; =20 /* Must be printed for any ABI < LANDLOCK_ABI_LAST. */ fprintf(stderr, @@ -644,6 +648,13 @@ int main(const int argc, char *const argv[], char *con= st *const envp) goto err_close_ruleset; } =20 + /* Don't require this env to be present. */ + if (no_inherit_supported && getenv(ENV_FS_NO_INHERIT_NAME)) { + if (populate_ruleset_fs(ENV_FS_NO_INHERIT_NAME, ruleset_fd, 0, + LANDLOCK_ADD_RULE_NO_INHERIT)) + goto err_close_ruleset; + } + if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd, LANDLOCK_ACCESS_NET_BIND_TCP, 0)) { goto err_close_ruleset; --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f177.google.com (mail-yw1-f177.google.com [209.85.128.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 59EA5364E9A for ; Fri, 29 May 2026 01:52:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019565; cv=none; b=KtaD/R7tqdZiZTcingjNha+erHpRZQzxLDREGAUUolhL1Fc+7eAkcSu/7QnwoNwBTjocMN1x1nBgzZkLpHFulAGHyD87257xqoEHY4iCgznFVzl22BL2CrAjGZ3UCv2srXIyLripklFbVDGyTsJuQyXyg3eg7sBG9SPdYLqEQns= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019565; c=relaxed/simple; bh=PxWTSJcZjWDmMHYkUewv0UtXCB9psPHTCWUWYSPymSg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lOs70UeXfl1LekI7peEjJBxWXf3+tB/DR7wyLDNzO7oC3uUwC3ZLfuYSK3f4oSOf4htQDhwI1XbuJTN2VzgJw7ug136hHdD4GU3viTU2X1CSJ27xNPTBD0NeWRDzLqA62i1wBzHswHBdiCsYCaHLP/4H3HtuzgOvsnteIoXw+Aw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=DtW4s2YP; arc=none smtp.client-ip=209.85.128.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DtW4s2YP" Received: by mail-yw1-f177.google.com with SMTP id 00721157ae682-7dd7818ac2aso6522907b3.1 for ; Thu, 28 May 2026 18:52:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019562; x=1780624362; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=3c98JrFKnAAYycYa6ckPFwB8uVY2q85M51B8MtK4Ddo=; b=DtW4s2YP/jd9/sXcaZDEXsXkDyj105PMoDzO5sqvzcr4VDaE7RhVrn8oloCGZyXSvL qVJbw8+oBfeNhcX+tc1X8lBivY+KSZ6SgmKoCdOaA6pJNfJUkYGTiJodWByWBbTXPHGa /ZjZ879nhbTJ0o7X83DZFH+ucSF7YjxAWc1jIVt5AUTpUTex246ghOjZEbLieryMw+03 8Q5KiHoYJu45OpE/LZj8IeJrvXD6ws5sYjjt/SYbbvvI/P5rCwL1abSFpGA7F+Zj49qm EnPQke6R0xcAjWKmz6zgxnwVQbuTue1qIm+kJlF10UjajM6RsPk5E7oc7grTwXZT8XOE 6hGw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019562; x=1780624362; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=3c98JrFKnAAYycYa6ckPFwB8uVY2q85M51B8MtK4Ddo=; b=mQP4o8TW+4T/AnfB0jf09FPpYj8b0Y2JFg06POpgdTEuT9mTOYFYF3RInHqseAhnQC l9i09Bg7KsB4/fZFtnpdsRAJGoWmf/upOFwFnV1a4IDpv13wHrA2AvUkKS4s/wPzTsq6 C00ciNujIduB1dfEZWEtNtETSJzSH1iLPK2pG/yyPo4qHFcOBSb+ZgRHhFf6+0DtVKu1 KWAXLjKbm9j7pxnQbdfuY4iF/+uw6NyRM+SYWhsjMCAKaQG/btnPS9xeyBGzYFz3H6fj sJZJha09fnLp/hiXG+fR1hBG3wkMQoZ5ddQqx3tsCcrJ8nTo+hUwWzAWXwpV8xS87FTn FVDQ== X-Gm-Message-State: AOJu0Yza8nhZjc6BssR4FLvNhOSSA7pVjynXfKnxXAcVD7RnQBJVgc+w EV72I2NviOoDsBCG5QSlO1kL75/+bQ0MbWtUTktxhVwGlLMisuCHwR94 X-Gm-Gg: Acq92OGCIytxpbGq2GJgmbLtFhjGu52O3wCD9/ZRtYhdjN9AoKAmci1aRARYF8V0Uar I/9Nljf6RnZrDNV92BxXjq2pfVLnUfm51Q1DPn5IlXe9691SnJIThIkjIX50PRVMS0Aw5xvukKJ mJC7fxdPcRvs+KwiwtTFTI0fvAZtchC0bAWOeWTHQpYWOJAretpHBgEdZIucWKtdX+E8h1+7r34 wPAN+qdCQMRyAjv++rO2ZUy63HMUmTAm72HNtZXSeA+j1SF2HVPXbywMwcrGYUG1V9QkbtWU8Ai 77hry+iaFpW7VGFH3oYHL6g+lNIA1QEe+bW2IEGIQENXk4kfUqMSLg93uR3qyxAZi9B66DGqEv+ vYa6/kthp+uS1lni1HDfWK1ZovXDfS3s+bp/qtW/UUSaRw1GXmPBPCPM4tq9ZZzcXOWVIhqphdK j2BDXYF74wT3gALNgUUlZc7GDemG0B1z/AEM6iqfBM8ZISeV2i319cH+LNWabPDDV4PkAhe++wm 7/shCLYaNk= X-Received: by 2002:a05:690c:7307:b0:7bd:7d69:7660 with SMTP id 00721157ae682-7de47e26cb6mr6564497b3.43.1780019562356; Thu, 28 May 2026 18:52:42 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:41 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v8 09/10] selftests/landlock: Add selftests for LANDLOCK_ADD_RULE_NO_INHERIT Date: Thu, 28 May 2026 21:52:08 -0400 Message-ID: <20260529015210.500291-10-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add test coverage for the new flag: - New layout1_no_inherit fixture with five variants covering NO_INHERIT on leaf, middle, and root directories, RW-over-RO expansion, and a regular file target. Three tests per variant exercise inheritance blocking, topology sealing, and layered (multi-domain) NO_INHERIT. - A new layout4_disconnected_leafs variant exercising NO_INHERIT applied through a bind mount, asserting that ancestors in both the bind and source paths are sealed. - A new audit_no_inherit fixture verifying that the flag interacts correctly with the quiet flag: a quiet ancestor does not suppress audit on a descendant that has crossed a NO_INHERIT boundary. Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Reorganized the new fs_test.c coverage around fixtures and variants instead of one TEST_F_FORK per scenario: =20 - New layout1_no_inherit fixture with five FIXTURE_VARIANT_ADD cases (rw_parent_ro_leaf, rw_parent_ro_middle, rw_parent_ro_root, ro_parent_rw_middle, rw_parent_read_file) collapse what were eight near-duplicate layout1 tests in v7 into three shared tests (blocks_inheritance, seals_topology, layered_no_inherit). =20 - New layout4_disconnected_leafs variant 'no_inherit_mount' with a single 'no_inherit_seals_mount' test replaces the four v7 layout4 tests (no_inherit_mount_parent_{rename,rmdir,link} and no_inherit_source_parent_rename) by exercising all four sealed topology operations in one test. =20 - New audit_no_inherit fixture with three variants (parent_is_logged, blocks_quiet_inheritance, quiet_parent) covers the quiet/no_inherit interaction previously inlined into an ad hoc audit test. =20 * Net change: 705 added lines in v7 -> 419 added lines in v8, with equivalent coverage. tools/testing/selftests/landlock/fs_test.c | 419 +++++++++++++++++++++ 1 file changed, 419 insertions(+) diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/sel= ftests/landlock/fs_test.c index 2e32295258f9..625ff1afecb0 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -1429,6 +1429,224 @@ TEST_F_FORK(layout1, inherit_superset) ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); } =20 +FIXTURE(layout1_no_inherit) {}; + +FIXTURE_SETUP(layout1_no_inherit) +{ + prepare_layout(_metadata); + create_layout1(_metadata); +} + +FIXTURE_TEARDOWN_PARENT(layout1_no_inherit) +{ + remove_layout1(_metadata); + cleanup_layout(_metadata); +} + +FIXTURE_VARIANT(layout1_no_inherit) +{ + const char *ni_path; + const __u64 ni_access; + const char *ni_file; + const char *desc_file; + const int expected_ni_write; + const int expected_ni_read; + const int expected_desc_write; + const int expected_desc_read; +}; + +/* NO_INHERIT on leaf directory: blocks parent's RW, grants only RO. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(layout1_no_inherit, rw_parent_ro_leaf) { + /* clang-format on */ + .ni_path =3D TMP_DIR "/s1d1/s1d2/s1d3", + .ni_access =3D ACCESS_RO, + .ni_file =3D TMP_DIR "/s1d1/s1d2/s1d3/f1", + .desc_file =3D TMP_DIR "/s1d1/s1d2/s1d3/f2", + .expected_ni_write =3D EACCES, + .expected_ni_read =3D 0, + .expected_desc_write =3D EACCES, + .expected_desc_read =3D 0, +}; + +/* NO_INHERIT on middle directory: blocks parent's RW for all descendants.= */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(layout1_no_inherit, rw_parent_ro_middle) { + /* clang-format on */ + .ni_path =3D TMP_DIR "/s1d1/s1d2", + .ni_access =3D ACCESS_RO, + .ni_file =3D TMP_DIR "/s1d1/s1d2/f1", + .desc_file =3D TMP_DIR "/s1d1/s1d2/s1d3/f1", + .expected_ni_write =3D EACCES, + .expected_ni_read =3D 0, + .expected_desc_write =3D EACCES, + .expected_desc_read =3D 0, +}; + +/* NO_INHERIT on root directory: blocks parent's RW for entire subtree. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(layout1_no_inherit, rw_parent_ro_root) { + /* clang-format on */ + .ni_path =3D TMP_DIR "/s1d1", + .ni_access =3D ACCESS_RO, + .ni_file =3D TMP_DIR "/s1d1/f1", + .desc_file =3D TMP_DIR "/s1d1/s1d2/s1d3/f1", + .expected_ni_write =3D EACCES, + .expected_ni_read =3D 0, + .expected_desc_write =3D EACCES, + .expected_desc_read =3D 0, +}; + +/* NO_INHERIT with RW access expands parent's RO to RW. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(layout1_no_inherit, ro_parent_rw_middle) { + /* clang-format on */ + .ni_path =3D TMP_DIR "/s1d1/s1d2", + .ni_access =3D ACCESS_RW, + .ni_file =3D TMP_DIR "/s1d1/s1d2/f1", + .desc_file =3D TMP_DIR "/s1d1/s1d2/s1d3/f1", + .expected_ni_write =3D 0, + .expected_ni_read =3D 0, + .expected_desc_write =3D 0, + .expected_desc_read =3D 0, +}; + +/* NO_INHERIT on a file: file gets only its explicit READ_FILE access. */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(layout1_no_inherit, rw_parent_read_file) { + /* clang-format on */ + .ni_path =3D TMP_DIR "/s1d1/s1d2/f1", + .ni_access =3D LANDLOCK_ACCESS_FS_READ_FILE, + .ni_file =3D TMP_DIR "/s1d1/s1d2/f1", + .desc_file =3D TMP_DIR "/s1d1/s1d2/f2", + .expected_ni_write =3D EACCES, + .expected_ni_read =3D 0, + .expected_desc_write =3D 0, + .expected_desc_read =3D 0, +}; + +TEST_F_FORK(layout1_no_inherit, blocks_inheritance) +{ + struct landlock_ruleset_attr ruleset_attr =3D { + .handled_access_fs =3D ACCESS_RW, + }; + int ruleset_fd; + + /* RO variants: TMP_DIR gets RO instead of RW. */ + if (variant->ni_access =3D=3D ACCESS_RW) + ruleset_attr.handled_access_fs |=3D LANDLOCK_ACCESS_FS_READ_DIR; + + ruleset_fd =3D + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + if (variant->ni_access =3D=3D ACCESS_RW) + add_path_beneath(_metadata, ruleset_fd, ACCESS_RO, TMP_DIR, 0); + else + add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, TMP_DIR, 0); + + add_path_beneath(_metadata, ruleset_fd, variant->ni_access, + variant->ni_path, LANDLOCK_ADD_RULE_NO_INHERIT); + + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + EXPECT_EQ(variant->expected_ni_write, + test_open(variant->ni_file, O_WRONLY)); + EXPECT_EQ(variant->expected_ni_read, + test_open(variant->ni_file, O_RDONLY)); + + if (variant->desc_file !=3D variant->ni_file) { + EXPECT_EQ(variant->expected_desc_write, + test_open(variant->desc_file, O_WRONLY)); + EXPECT_EQ(variant->expected_desc_read, + test_open(variant->desc_file, O_RDONLY)); + } +} + +TEST_F_FORK(layout1_no_inherit, seals_topology) +{ + int ruleset_fd; + struct landlock_ruleset_attr ruleset_attr =3D { + .handled_access_fs =3D ACCESS_RW | LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_REMOVE_FILE | + LANDLOCK_ACCESS_FS_REMOVE_DIR, + }; + + ruleset_fd =3D + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + add_path_beneath(_metadata, ruleset_fd, + ACCESS_RW | LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_REMOVE_FILE | + LANDLOCK_ACCESS_FS_REMOVE_DIR, + TMP_DIR, 0); + add_path_beneath(_metadata, ruleset_fd, variant->ni_access, + variant->ni_path, LANDLOCK_ADD_RULE_NO_INHERIT); + + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* The directory bearing NO_INHERIT cannot be renamed or removed. */ + ASSERT_EQ(-1, rename(variant->ni_path, TMP_DIR "/ni_renamed")); + ASSERT_EQ(EACCES, errno); + + /* + * Content inside the NO_INHERIT directory is still mutable + * (if the access rights permit it). + */ + if (variant->ni_access & LANDLOCK_ACCESS_FS_REMOVE_FILE) { + ASSERT_EQ(0, unlink(variant->ni_file)); + } else { + ASSERT_EQ(-1, unlink(variant->ni_file)); + } + + /* Unrelated operations outside the sealed branch still work. */ + ASSERT_EQ(0, unlink(file1_s2d1)); + ASSERT_EQ(0, mknod(file1_s2d1, S_IFREG | 0700, 0)); +} + +TEST_F_FORK(layout1_no_inherit, layered_no_inherit) +{ + const struct rule layer_rules[] =3D { + { + .path =3D TMP_DIR, + .access =3D ACCESS_RW | LANDLOCK_ACCESS_FS_REMOVE_FILE, + }, + {}, + }; + int ruleset_fd; + + /* Layer 1: RW on TMP_DIR. */ + ruleset_fd =3D create_ruleset(_metadata, + ACCESS_RW | LANDLOCK_ACCESS_FS_REMOVE_FILE, + layer_rules); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Layer 2: NO_INHERIT on the target. */ + ruleset_fd =3D create_ruleset(_metadata, + ACCESS_RW | LANDLOCK_ACCESS_FS_REMOVE_FILE, + layer_rules); + ASSERT_LE(0, ruleset_fd); + add_path_beneath(_metadata, ruleset_fd, variant->ni_access, + variant->ni_path, LANDLOCK_ADD_RULE_NO_INHERIT); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* The target path cannot be renamed. */ + ASSERT_EQ(-1, rename(variant->ni_path, TMP_DIR "/ni_renamed_layered")); + ASSERT_EQ(EACCES, errno); + + /* Content at NI path respects the NO_INHERIT access from layer 2. */ + EXPECT_EQ(variant->expected_ni_write, + test_open(variant->ni_file, O_WRONLY)); + EXPECT_EQ(variant->expected_ni_read, + test_open(variant->ni_file, O_RDONLY)); +} + TEST_F_FORK(layout0, max_layers) { int i, err; @@ -5571,6 +5789,25 @@ FIXTURE_VARIANT(layout4_disconnected_leafs) const int expected_exchange_result; /* Expected result of the call to renameat([fd:s1d42]/f4, [fd:s1d42]/f5).= */ const int expected_same_dir_rename_result; + + /* + * If true, a NO_INHERIT rule is set on s1d41 (via the bind mount + * at s2d2). Used by the no_inherit_mount test. + */ + bool no_inherit_on_s1d41; + /* + * Access rights used for the optional NO_INHERIT rule on s1d41. + */ + const __u64 no_inherit_access; + /* + * Expected result of renaming s1d31 (parent of s1d41 within the + * mount) when no_inherit_on_s1d41 is set. + */ + const int expected_parent_rename; + /* + * Expected result of rmdir on s1d31, when no_inherit_on_s1d41 is set. + */ + const int expected_parent_rmdir; }; =20 /* clang-format off */ @@ -5823,6 +6060,26 @@ FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, f1_f= 2_f3) { .expected_exchange_result =3D EACCES, }; =20 +/* + * NO_INHERIT variant: s1d41 is protected with ACCESS_RO via the bind moun= t. + * Parents within the mount are sealed against topology changes. + */ +/* clang-format off */ +FIXTURE_VARIANT_ADD(layout4_disconnected_leafs, no_inherit_mount) { + /* clang-format on */ + .allowed_f1 =3D LANDLOCK_ACCESS_FS_READ_FILE, + .allowed_f2 =3D LANDLOCK_ACCESS_FS_READ_FILE, + .allowed_f3 =3D LANDLOCK_ACCESS_FS_READ_FILE, + .expected_read_result =3D 0, + .expected_rename_result =3D EACCES, + .expected_exchange_result =3D EACCES, + .expected_same_dir_rename_result =3D EACCES, + .no_inherit_on_s1d41 =3D true, + .no_inherit_access =3D ACCESS_RO, + .expected_parent_rename =3D EACCES, + .expected_parent_rmdir =3D EACCES, +}; + TEST_F_FORK(layout4_disconnected_leafs, read_rename_exchange) { const __u64 handled_access =3D @@ -5931,6 +6188,70 @@ TEST_F_FORK(layout4_disconnected_leafs, read_rename_= exchange) test_renameat(s1d42_bind_fd, "f4", s1d42_bind_fd, "f5")); } =20 +/* + * When s1d41 (accessed via the bind mount at s2d2) is protected with + * NO_INHERIT, its parent directories within the mount are sealed from + * topology changes. Other variants do not exercise NO_INHERIT and skip + * this test. + */ +TEST_F_FORK(layout4_disconnected_leafs, no_inherit_seals_mount) +{ + struct landlock_ruleset_attr ruleset_attr =3D { + .handled_access_fs =3D ACCESS_RW | LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_REMOVE_FILE | + LANDLOCK_ACCESS_FS_REMOVE_DIR, + }; + int ruleset_fd, s1d41_bind_fd; + + if (!variant->no_inherit_on_s1d41) + SKIP(return, "variant does not set NO_INHERIT on s1d41"); + + ruleset_fd =3D + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + add_path_beneath(_metadata, ruleset_fd, + ACCESS_RW | LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_REMOVE_FILE | + LANDLOCK_ACCESS_FS_REMOVE_DIR, + TMP_DIR, 0); + + s1d41_bind_fd =3D open(TMP_DIR "/s2d1/s2d2/s1d31/s1d41", + O_DIRECTORY | O_PATH | O_CLOEXEC); + ASSERT_LE(0, s1d41_bind_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &(struct landlock_path_beneath_attr){ + .parent_fd =3D s1d41_bind_fd, + .allowed_access =3D + variant->no_inherit_access, + }, + LANDLOCK_ADD_RULE_NO_INHERIT)); + EXPECT_EQ(0, close(s1d41_bind_fd)); + + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Parent of s1d41 within the mount is sealed. */ + ASSERT_EQ(-1, rmdir(TMP_DIR "/s2d1/s2d2/s1d31")); + ASSERT_EQ(variant->expected_parent_rmdir, errno); + + ASSERT_EQ(-1, rename(TMP_DIR "/s2d1/s2d2/s1d31", + TMP_DIR "/s2d1/s2d2/s1d31_renamed")); + ASSERT_EQ(variant->expected_parent_rename, errno); + + /* Sibling directories outside the sealed chain are free. */ + ASSERT_EQ(0, rename(TMP_DIR "/s2d1/s2d2/s1d32", + TMP_DIR "/s2d1/s2d2/s1d32_renamed")); + ASSERT_EQ(0, rename(TMP_DIR "/s2d1/s2d2/s1d32_renamed", + TMP_DIR "/s2d1/s2d2/s1d32")); + + /* The mount source parent hierarchy is also sealed. */ + ASSERT_EQ(-1, rename(TMP_DIR "/s1d1/s1d2/s1d31", + TMP_DIR "/s1d1/s1d2/s1d31_renamed")); + ASSERT_EQ(variant->expected_parent_rename, errno); +} + /* * layout5_disconnected_branch before rename: * @@ -7358,6 +7679,104 @@ TEST_F(audit_layout1, write_file) EXPECT_EQ(1, records.domain); } =20 +FIXTURE(audit_no_inherit) +{ + struct audit_filter audit_filter; + int audit_fd; +}; + +FIXTURE_SETUP(audit_no_inherit) +{ + prepare_layout(_metadata); + create_layout1(_metadata); + + set_cap(_metadata, CAP_AUDIT_CONTROL); + self->audit_fd =3D audit_init_with_exe_filter(&self->audit_filter); + EXPECT_LE(0, self->audit_fd); + clear_cap(_metadata, CAP_AUDIT_CONTROL); +} + +FIXTURE_TEARDOWN_PARENT(audit_no_inherit) +{ + remove_layout1(_metadata); + cleanup_layout(_metadata); + + EXPECT_EQ(0, audit_cleanup(-1, NULL)); +} + +FIXTURE_VARIANT(audit_no_inherit) +{ + bool parent_quiet; + const char *test_path; + bool expect_audit_log; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(audit_no_inherit, parent_is_logged) { + /* clang-format on */ + .parent_quiet =3D false, + .test_path =3D TMP_DIR "/s1d1/s1d2/f1", + .expect_audit_log =3D true, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(audit_no_inherit, blocks_quiet_inheritance) { + /* clang-format on */ + .parent_quiet =3D true, + .test_path =3D TMP_DIR "/s1d1/s1d2/s1d3/f1", + .expect_audit_log =3D true, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(audit_no_inherit, quiet_parent) { + /* clang-format on */ + .parent_quiet =3D true, + .test_path =3D TMP_DIR "/s1d1/f1", + .expect_audit_log =3D false, +}; + +TEST_F(audit_no_inherit, no_inherit_audit) +{ + struct audit_records records; + struct landlock_ruleset_attr ruleset_attr =3D { + .handled_access_fs =3D ACCESS_RW, + .quiet_access_fs =3D variant->parent_quiet ? ACCESS_RW : 0, + }; + int ruleset_fd; + + ruleset_fd =3D landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + if (variant->parent_quiet) + add_path_beneath(_metadata, ruleset_fd, ACCESS_RO, dir_s1d1, + LANDLOCK_ADD_RULE_QUIET); + else + add_path_beneath(_metadata, ruleset_fd, ACCESS_RO, dir_s1d1, 0); + + add_path_beneath(_metadata, ruleset_fd, ACCESS_RO, dir_s1d3, + LANDLOCK_ADD_RULE_NO_INHERIT); + + enforce_ruleset(_metadata, ruleset_fd); + + EXPECT_EQ(EACCES, test_open(variant->test_path, O_WRONLY)); + if (variant->expect_audit_log) { + EXPECT_EQ(0, matches_log_fs(_metadata, self->audit_fd, + "fs\\.write_file", + variant->test_path)); + } else { + EXPECT_NE(0, matches_log_fs(_metadata, self->audit_fd, + "fs\\.write_file", + variant->test_path)); + } + + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); + EXPECT_EQ(0, records.access); + EXPECT_EQ(variant->expect_audit_log ? 1 : 0, records.domain); + + EXPECT_EQ(0, close(ruleset_fd)); +} + TEST_F(audit_layout1, read_file) { struct audit_records records; --=20 2.53.0 From nobody Mon Jun 8 13:29:44 2026 Received: from mail-yw1-f181.google.com (mail-yw1-f181.google.com [209.85.128.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 53110362136 for ; Fri, 29 May 2026 01:52:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019566; cv=none; b=ERk2GxN4kfVlyLaazf5iNNgYOD+Hj+cML9FGh4yXjbLa+qA53hJbluaRzZJPb7Tdwy8m2+OC+SUAfeeNwoNT2ZvOS6CRTtPSSnenoYpX668JZ10gQUAGiLhcqz5GY2Cj75C/vCIp5MiYpnKnripMxOENt1I8wzuAWvsL/5J1lgg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780019566; c=relaxed/simple; bh=moBOfUmbdHS94qSP33raegmaKYYJoraho4benxR3JdA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=V//az7A2a9USFGPCkRZwz+1Zi/YEB1QibL+ZOWmu613AoNa3FrpVSvoPIouRfEPUKs764knPh7Pdds2u7IGkmfv8oUs3R8Ub8FMoRDGJEri3qqCKHLm1Ey1Yu3E+7XbDmWgyBTKBpXNY1LX4B/1nmBQ5kmgV8Ba8SJpPN4SlQeQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=qZEJg+O/; arc=none smtp.client-ip=209.85.128.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="qZEJg+O/" Received: by mail-yw1-f181.google.com with SMTP id 00721157ae682-7dca5a81be2so13999067b3.2 for ; Thu, 28 May 2026 18:52:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780019564; x=1780624364; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=6D2ipigGm4Qh+rWj0g1Z+KaYmugk/663SOJobhVAuGc=; b=qZEJg+O/Bdq0uL6wPDmUZW7+aHKML8h+iqMn3nOO4NwEZJkDelUOrm0ZMJmbtxKnqP A3a9k28y1pd08mhYso0SE6x/U9ko562rZ2Y4nX7U5Zfdqg6gnPFxd7v/abO1agr7lQfj jcBd9HKoSNWEQjj7YeGknHk2H83bLNdN2/+Qu+l/NUt+HTqPpnKcUZPJXzkOWTVGqRlv Co8Xx4+OWV0vagvSTtXGsEljPmzmgs3W80XmKNlSnpQNPupEABofoVTXGZIeC/n2GZ12 9/Zdt7bqBQquJNRMgrSDGUrVYROn7Ix2GwQOiNbb8ZqLblD80YP/WkwjjN5HgWl6phCx 3wgQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780019564; x=1780624364; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=6D2ipigGm4Qh+rWj0g1Z+KaYmugk/663SOJobhVAuGc=; b=ISdU0dQTchmNiDmlCcXZdoDUdudspacRFVitSaK97N+IPqeIkP9MpbIlmiLkEOBaCx Kffh+ATvgqv76dLMN27WK0tIMCM89CsX8WrTO2E97kOjFjjsYrkIwCgNrLkcEg4O+TPH pLQN/lyxdPhH/IWRJNCUpw0W3U7TGGUfJQcKHtLZPI10VTTnqQwJiYX2RwzbnEQqeY+8 RaSNCR4GpATqKqRFb2Rv2vVwhbHOrZC9FCF7qe6xB2AR1KgDnElVLicumj+Ay8SuwWqH 1aiazIx7jCwzAcN/nvjPtmrYFeTk2XexmXDclVXveGxLDk4bBVxaoKQOgVafC4GMPjng YZ1w== X-Gm-Message-State: AOJu0Yypr8S2fjvn1C2dWli+94jqvR3jZlBLIytjpGn0XrupB/scnx2n rWFcpP9GPkdaUDXF5Tg0abD/MJ1Yo99wMwXalUCWKuObC/q2H9tKGFyROEDWtw== X-Gm-Gg: Acq92OFJGSc44vN+U8SV5ZnrSTAogzVFcjRZWmyli0pEDG2bfeWKJIg0+t4/Ahmh8oU 0VagdCmr37l4+ayQVfllIdBru4Nowfug4hKjm75qK4atod+zLuPookReI3OFqNy4imdgwCeVxG2 rUwTWm99q6XUIB4vENW/C+ins2mXbAR6xfLd1eBB9Y+Rz4R+NjEmXJYhZ8FKSXTzWg7yhKCS04R H/mdSxuCl2NNjGikD7Ud9R4/owa3du1qfKM7O7asszH8BN65GQi85rpDSrktZv66U3fyfac7HYx dFzk+KsXw6aVmGlW7qdQWyDg8yykxxeun+sgXi7BCpjT1gc/E0LoddQ6RvPsNIdTyLHBgb6O8WW 87456VvaOPjP0qbRFwU1sNuCEpRZVYep3RoGFiJX5Qf7JMUonzV5JR8vM5+WsgxkTUw3jhhzdyl zc0glwY1wbVDgiDhC7Y6FXWXbZfAcEmVywYRHy8lAmbcytCme54QcPKl5lnnsuOLoB+z7q6ZtW6 B8cjyEVGes= X-Received: by 2002:a05:690c:4391:b0:7de:795e:e9d7 with SMTP id 00721157ae682-7de795ee9f4mr2943067b3.39.1780019564308; Thu, 28 May 2026 18:52:44 -0700 (PDT) Received: from zenbox.prizrak.me ([2600:1700:18fb:6011:7a41:d368:8442:1cb2]) by smtp.gmail.com with ESMTPSA id 00721157ae682-7de6d1f3943sm1284717b3.26.2026.05.28.18.52.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 28 May 2026 18:52:44 -0700 (PDT) From: Justin Suess To: gnoack3000@gmail.com, mic@digikod.net Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, Justin Suess Subject: [PATCH v8 10/10] landlock: Add KUnit tests for LANDLOCK_ADD_RULE_NO_INHERIT Date: Thu, 28 May 2026 21:52:09 -0400 Message-ID: <20260529015210.500291-11-utilityemal77@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260529015210.500291-1-utilityemal77@gmail.com> References: <20260529015210.500291-1-utilityemal77@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add the landlock_ruleset KUnit suite with five tests for the no_inherit handling in landlock_unmask_layers(): - test_unmask_no_inherit_propagates: a rule with no_inherit unmasks access and sets the no_inherit bit on the layer mask. - test_unmask_no_inherit_skip: a layer with no_inherit already set in the mask is skipped (no access removal). - test_unmask_no_inherit_both_set: when both rule and mask have no_inherit, the skip still happens and the bit stays set. - test_unmask_multilayer_no_inherit: no_inherit on one layer of a multi-layer rule only affects that layer. - test_unmask_no_inherit_sequential: applying a descendant rule (no_inherit) followed by an ancestor rule causes the ancestor to be skipped, modeling a path walk. Signed-off-by: Justin Suess --- Notes: v7..v8 changes: =20 * Renamed patch from 'Implement KUnit test' to 'Add KUnit tests' (now plural). * Replaced the single test_unmask_layers_no_inherit() case with five focused tests aligned with the new per-layer no_inherit bit added to struct layer_mask in patch 6: =20 - test_unmask_no_inherit_propagates - test_unmask_no_inherit_skip - test_unmask_no_inherit_both_set - test_unmask_multilayer_no_inherit - test_unmask_no_inherit_sequential =20 * Added alloc_rule() and fill_masks() helpers to share setup across the new tests. security/landlock/ruleset.c | 182 ++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index c78e2b2d73ff..9f47d106aca3 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -6,6 +6,7 @@ * Copyright =C2=A9 2018-2020 ANSSI */ =20 +#include #include #include #include @@ -766,3 +767,184 @@ landlock_init_layer_masks(const struct landlock_rules= et *const domain, =20 return handled_accesses; } + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +/* + * Helper to allocate a rule with @num_layers layers and initialize + * its num_layers field. Caller must fill in individual layers. + */ +static struct landlock_rule *alloc_rule(struct kunit *test, u32 num_layers) +{ + struct landlock_rule *rule; + + rule =3D kzalloc(struct_size(rule, layers, num_layers), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, rule); + rule->num_layers =3D num_layers; + return rule; +} + +/* + * Build a layer_masks with the first @num_layers layers' access set to + * @val, and all no_inherit flags cleared. Layers beyond @num_layers stay + * zeroed, matching what landlock_init_layer_masks() produces for a domain + * with that many layers. + */ +static void fill_masks(struct layer_masks *masks, access_mask_t val, + size_t num_layers) +{ + memset(masks, 0, sizeof(*masks)); + for (size_t i =3D 0; i < num_layers; i++) + masks->layers[i].access =3D val; +} + +/* Verify that a rule with no_inherit unmasks access and propagates the fl= ag. */ +static void test_unmask_no_inherit_propagates(struct kunit *const test) +{ + struct landlock_rule *rule =3D alloc_rule(test, 1); + struct layer_masks masks; + const access_mask_t req =3D BIT_ULL(0) | BIT_ULL(1); + + rule->layers[0].level =3D 1; + rule->layers[0].access =3D BIT_ULL(0); + rule->layers[0].flags.no_inherit =3D true; + + fill_masks(&masks, req, 1); + landlock_unmask_layers(rule, &masks); + + /* access bit 0 should be cleared, bit 1 remains */ + KUNIT_EXPECT_EQ(test, (access_mask_t)masks.layers[0].access, + BIT_ULL(1)); + KUNIT_EXPECT_TRUE(test, masks.layers[0].no_inherit); + KUNIT_EXPECT_EQ(test, (access_mask_t)masks.layers[1].access, 0); + kfree(rule); +} + +/* Verify that a pre-set no_inherit in the mask causes the layer to be ski= pped. */ +static void test_unmask_no_inherit_skip(struct kunit *const test) +{ + struct landlock_rule *rule =3D alloc_rule(test, 1); + struct layer_masks masks; + const access_mask_t req =3D BIT_ULL(0); + + rule->layers[0].level =3D 1; + rule->layers[0].access =3D BIT_ULL(0); + + fill_masks(&masks, req, 1); + masks.layers[0].no_inherit =3D true; + landlock_unmask_layers(rule, &masks); + + /* bit 0 should NOT be cleared because layer was skipped */ + KUNIT_EXPECT_EQ(test, (access_mask_t)masks.layers[0].access, req); + KUNIT_EXPECT_TRUE(test, masks.layers[0].no_inherit); + kfree(rule); +} + +/* + * Verify that no_inherit on the rule is still set when the mask already + * has no_inherit (the skip prevents access removal but the flag propagate= s). + */ +static void test_unmask_no_inherit_both_set(struct kunit *const test) +{ + struct landlock_rule *rule =3D alloc_rule(test, 1); + struct layer_masks masks; + const access_mask_t req =3D BIT_ULL(0); + + rule->layers[0].level =3D 1; + rule->layers[0].access =3D BIT_ULL(0); + rule->layers[0].flags.no_inherit =3D true; + + fill_masks(&masks, req, 1); + masks.layers[0].no_inherit =3D true; + landlock_unmask_layers(rule, &masks); + + KUNIT_EXPECT_EQ(test, (access_mask_t)masks.layers[0].access, req); + KUNIT_EXPECT_TRUE(test, masks.layers[0].no_inherit); + kfree(rule); +} + +/* + * Verify that no_inherit on layer 1 of a multi-layer rule only affects + * layer 1; layer 2 still contributes normally. + */ +static void test_unmask_multilayer_no_inherit(struct kunit *const test) +{ + struct landlock_rule *rule =3D alloc_rule(test, 2); + struct layer_masks masks; + const access_mask_t req =3D BIT_ULL(0) | BIT_ULL(1); + + rule->layers[0].level =3D 1; + rule->layers[0].access =3D BIT_ULL(0); + rule->layers[0].flags.no_inherit =3D true; + + rule->layers[1].level =3D 2; + rule->layers[1].access =3D BIT_ULL(1); + + fill_masks(&masks, req, 2); + landlock_unmask_layers(rule, &masks); + + /* Layer 1: bit 0 cleared, no_inherit set */ + KUNIT_EXPECT_EQ(test, (access_mask_t)masks.layers[0].access, BIT_ULL(1)); + KUNIT_EXPECT_TRUE(test, masks.layers[0].no_inherit); + + /* Layer 2: bit 1 cleared, no_inherit not set */ + KUNIT_EXPECT_EQ(test, (access_mask_t)masks.layers[1].access, BIT_ULL(0)); + KUNIT_EXPECT_FALSE(test, masks.layers[1].no_inherit); + kfree(rule); +} + +/* + * Verify that when applying two rules sequentially (as happens during + * a path walk), no_inherit from the first rule prevents the second + * rule from contributing to that layer. + */ +static void test_unmask_no_inherit_sequential(struct kunit *const test) +{ + struct landlock_rule *rule1 =3D alloc_rule(test, 1); + struct landlock_rule *rule2 =3D alloc_rule(test, 1); + struct layer_masks masks; + const access_mask_t req =3D BIT_ULL(0) | BIT_ULL(1); + + /* Rule 1: no_inherit on layer 1, grants access bit 0 */ + rule1->layers[0].level =3D 1; + rule1->layers[0].access =3D BIT_ULL(0); + rule1->layers[0].flags.no_inherit =3D true; + + /* Rule 2: also on layer 1, grants access bit 1 (ancestor rule) */ + rule2->layers[0].level =3D 1; + rule2->layers[0].access =3D BIT_ULL(1); + + /* Apply rule1 first (descendant), then rule2 (ancestor) */ + fill_masks(&masks, req, 1); + landlock_unmask_layers(rule1, &masks); + landlock_unmask_layers(rule2, &masks); + + /* + * Rule2 should be skipped because rule1 set no_inherit. + * bit 0 cleared by rule1, bit 1 remains because rule2 skipped. + */ + KUNIT_EXPECT_EQ(test, (access_mask_t)masks.layers[0].access, BIT_ULL(1)); + KUNIT_EXPECT_TRUE(test, masks.layers[0].no_inherit); + kfree(rule1); + kfree(rule2); +} + +/* clang-format off */ +static struct kunit_case test_cases[] =3D { + KUNIT_CASE(test_unmask_no_inherit_propagates), + KUNIT_CASE(test_unmask_no_inherit_skip), + KUNIT_CASE(test_unmask_no_inherit_both_set), + KUNIT_CASE(test_unmask_multilayer_no_inherit), + KUNIT_CASE(test_unmask_no_inherit_sequential), + {} +}; +/* clang-format on */ + +static struct kunit_suite test_suite =3D { + .name =3D "landlock_ruleset", + .test_cases =3D test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ --=20 2.53.0