From nobody Mon Oct 6 15:12:20 2025 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (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 3B0C2222566; Sat, 19 Jul 2025 11:13:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752923597; cv=none; b=IuVbllqZKNBpBmEfvoETg7scMZxBVInwaPIp3azPPo5TBbsEuMuYB4whprcHb/8EaoPF0ITdH3LX9IqjlnYGw6LgzpW51aSxvk1Th7/+sazK7n4ZaEUS5Pqq6WuNNY1msRPSeNf3HuK6QGaBTg3hVVM1O17xGEKWLtNPrV5swhQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752923597; c=relaxed/simple; bh=iDJifj/ZhU51RDm0EpQQSmQnGYmcG8haC/LGgI5iLDU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KZbqiuQTH6qmsKRxIJ+PqF+gd4jRBka9JRdEjQfs9KCSUUyUxohkV6eYLvW9K/BjJxeThl2xQNutn31G69Il2M1ZvKzZs4SHOsA6B1dFlaygNqHVnvyty044sZrT5mrkrMYMsxRgpSNeMYAdgSf0gjH6EV6DllUvgT3dNcmgKlE= 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=LznxAa4D; arc=none smtp.client-ip=209.85.214.182 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="LznxAa4D" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-234c5b57557so27289415ad.3; Sat, 19 Jul 2025 04:13:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752923595; x=1753528395; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=ZjByKCvTZEF1xGfMFmwT1JaBMmtSxQ+whOxPUBJ5+Io=; b=LznxAa4D8B4Gg41t3vIVWJv80vxIh58FsPuLwamELavP3oJiV+P0UFqlWtmx4MdmV2 6DN32Krx9PxsJ4Au0fXEHYS2D2yNsoMAnEzlfUMUOP7T1LEsLRKNG8TTk+UM87TRGsO8 OrHof6TFyTsPD5fxTnZb6Gj30/aJGmogtmJHqEjfKwTqpfO4pIwe6m0ctXmmn2fn21MH UGnMkRlrWCk6Ok1vD4aLqdxZimr2Cd0UOlIMSq1YA32sXEeUbNAEmEg3AGMnIvq3Y9XD 5VjjjPVKcMyo7gIOIyxnlvZUxTKo/cdZ4oRXwOLCUZvGLhHmDYixV12Q0yiBVTdAEp4n 0QEA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752923595; x=1753528395; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ZjByKCvTZEF1xGfMFmwT1JaBMmtSxQ+whOxPUBJ5+Io=; b=u95rLHb1WnDwGj0/c6imvxATEM74otJN2WnHze8sx99pssST4oKN1CP5QDD6tbJy1t q6WqqDJslprdJVnngk/pOQPJrmRT0464bmwcA2fdQ9GtmO3fM2cKWyK5vXUQVBHPANjF yHGsnbS4uQFMtn8PQzthCep3Jw8b9HJJU0lMHW+/9PAe9PJBlNkPzoDZCl8I9/ojtIHY I/+3Y1Lsnc88kOwPn4MD0ZwWfEfakr7aDythE3JcZbO7RRWcyy3ArSeHnK7oA4jby+mi Mg7zcDYatCUkhpsKxMiBezElVOHGDMKzVpbGVrRbh9V7p+NYW91zzVdeWnP0eoQ1IDob ESgw== X-Forwarded-Encrypted: i=1; AJvYcCW9bH5WYK0Q7fSWoCSZKUgNKLkY3tqAnut6bJGNvFzzuTpQzOSFwHAvq8+qiK0rKtYjg1tf/NXfxLtsqeYcs7kf@vger.kernel.org, AJvYcCXizca/djppnrm/EnZjzerxcezMLYsjLWBbNqEXMPk0EC2aEVN9Fxz99oW3VBS7hNJ9eVwQoyXlAEzfi2w=@vger.kernel.org X-Gm-Message-State: AOJu0Yxa/roSrZl5kmF4z9NIdlyUSoSXAoSpClwd+ZfX35/0ZUwxRl8H bdtGUJ99xP7PgHQEKVLAum5GR3TXIOU2BKuXIyS7sl6d6Y+bw2H7Vet9 X-Gm-Gg: ASbGncvK0Jlkv6l8q9ePC8+Gsd0VaUrc8MgmE//AgCiREFTpbnGBS8DkGVNAlCHxxTA xm97Aqpt2QIvW6/S/yzLDxjA6bo1Cy1vU3bkCz0RlgjJZbmLUNCePsPsIyJDSU/J2spbGeghse1 3CBoJQVBFw5+/M5bC/5nfknR4wdYBLKbXe+clkbhr+SVw0xFWkxV2z0kdgfQp/dMt11TQoMKlWE PejIX3j/5eu0QT0YJDLLS5rJIS56tgEdGP5Nhzz6YJLGRsjM/855TSgRgdfJ2naW2jebtSdZ91o iIO6iNy0RyoEDbvkJKy/8GI5x0dXc7wj0EKB2ikU/ZsvSA13FsjnT+oKuFxR2He1sTxCnQv+giV dGD0k8evSgNtwoSl10l0r X-Google-Smtp-Source: AGHT+IG0u/3BM4162UOk4cT8N50hBTxDblT9eqv7wTs1fnB7ULc1mTo4uoyoShWrx6dn8rHaTBHZfQ== X-Received: by 2002:a17:902:ea01:b0:235:2403:77c7 with SMTP id d9443c01a7336-23e2576e462mr159838865ad.37.1752923595464; Sat, 19 Jul 2025 04:13:15 -0700 (PDT) Received: from [0.0.5.57] ([136.159.213.146]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-23e3b6b4c81sm27388875ad.114.2025.07.19.04.13.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 19 Jul 2025 04:13:15 -0700 (PDT) From: Abhinav Saxena Date: Sat, 19 Jul 2025 05:13:11 -0600 Subject: [PATCH RFC 1/4] landlock: add LANDLOCK_SCOPE_MEMFD_EXEC scope 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 Message-Id: <20250719-memfd-exec-v1-1-0ef7feba5821@gmail.com> References: <20250719-memfd-exec-v1-0-0ef7feba5821@gmail.com> In-Reply-To: <20250719-memfd-exec-v1-0-0ef7feba5821@gmail.com> To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G=C3=BCnther_Noack?= , Paul Moore , James Morris , "Serge E. Hallyn" , Shuah Khan , Nathan Chancellor , Nick Desaulniers , Bill Wendling , Justin Stitt Cc: linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Abhinav Saxena X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1752923593; l=3578; i=xandfury@gmail.com; s=20250614; h=from:subject:message-id; bh=iDJifj/ZhU51RDm0EpQQSmQnGYmcG8haC/LGgI5iLDU=; b=ZOXLrGW0W9QEKrXbhoVkDi78LrYBAUNgeQPX7CkuOST32Syj2llRiJTC6HzEjjW1yg5JJkEI7 uaGLyqCIzXSDFD0uOnqyqVtpbUvGTTHCL2b4oaMaWBJnT4qYZu981A8 X-Developer-Key: i=xandfury@gmail.com; a=ed25519; pk=YN6w7WNet8skqvMWxhG5BlAmtd1SQmo8If6Mofh4k44= Add new scope LANDLOCK_SCOPE_MEMFD_EXEC to restrict execution of anonymous memory file descriptors (memfd). This scope prevents execution of code through memfd files via execve() family syscalls and executable memory mappings. Update UAPI headers, limits, audit infrastructure, and kunit config to support the new scope. The scope follows existing Landlock scoping patterns for hierarchical domain enforcement. Signed-off-by: Abhinav Saxena --- include/uapi/linux/landlock.h | 5 +++++ security/landlock/.kunitconfig | 1 + security/landlock/audit.c | 4 ++++ security/landlock/audit.h | 1 + security/landlock/limits.h | 2 +- 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index f030adc462ee..5fa439b65aa6 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -364,10 +364,15 @@ struct landlock_net_port_attr { * related Landlock domain (e.g., a parent domain or a non-sandboxed pro= cess). * - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a s= ignal * to another process outside the domain. + * - %LANDLOCK_SCOPE_MEMFD_EXEC: Restrict a sandboxed process from executi= ng + * anonymous memory file descriptors (memfd). This prevents execution of + * code through memfd files via execve() family syscalls and executable + * memory mappings. */ /* clang-format off */ #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0) #define LANDLOCK_SCOPE_SIGNAL (1ULL << 1) +#define LANDLOCK_SCOPE_MEMFD_EXEC (1ULL << 2) /* clang-format on*/ =20 #endif /* _UAPI_LINUX_LANDLOCK_H */ diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig index f9423f01ac5b..a989785df65d 100644 --- a/security/landlock/.kunitconfig +++ b/security/landlock/.kunitconfig @@ -1,6 +1,7 @@ CONFIG_AUDIT=3Dy CONFIG_KUNIT=3Dy CONFIG_NET=3Dy +CONFIG_MEMFD_CREATE=3Dy CONFIG_SECURITY=3Dy CONFIG_SECURITY_LANDLOCK=3Dy CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=3Dy diff --git a/security/landlock/audit.c b/security/landlock/audit.c index c52d079cdb77..a439461d1b28 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -78,6 +78,10 @@ get_blocker(const enum landlock_request_type type, case LANDLOCK_REQUEST_SCOPE_SIGNAL: WARN_ON_ONCE(access_bit !=3D -1); return "scope.signal"; + + case LANDLOCK_REQUEST_SCOPE_MEMFD_EXEC: + WARN_ON_ONCE(access_bit !=3D -1); + return "scope.memfd_exec"; } =20 WARN_ON_ONCE(1); diff --git a/security/landlock/audit.h b/security/landlock/audit.h index 92428b7fc4d8..5a822bc50c4a 100644 --- a/security/landlock/audit.h +++ b/security/landlock/audit.h @@ -21,6 +21,7 @@ enum landlock_request_type { LANDLOCK_REQUEST_NET_ACCESS, LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, LANDLOCK_REQUEST_SCOPE_SIGNAL, + LANDLOCK_REQUEST_SCOPE_MEMFD_EXEC, }; =20 /* diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 65b5ff051674..130f925283fa 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -27,7 +27,7 @@ #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NE= T) =20 -#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL +#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_MEMFD_EXEC #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) =20 --=20 2.43.0 From nobody Mon Oct 6 15:12:20 2025 Received: from mail-pl1-f181.google.com (mail-pl1-f181.google.com [209.85.214.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 92F62238C3D; Sat, 19 Jul 2025 11:13:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752923599; cv=none; b=jABSucary5ThyaUEGuYkYeYObXmaToxzEYMWOZgJIGg+GKE3HAY+cmYbqtgDX+KcTatmSVjeHujhRSr3lT2a4q8Zfjvlv6Y7PI/vTwchX/WMb4XOKhoLAHSlbZn5gZhEI0VKNEa4n5TnhC6RiBKyYKnQ5OGBstG6ybdVacuH/64= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752923599; c=relaxed/simple; bh=jTePs6g/xDy0KJam8dPT5XZrMR7zmL6IWyOVHqVv93U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=aPHBzLHgic8/OmVzoAOIODsiSiarlT1MTdxvd21fPpNUBfu2qEcRNpRnBoqk6hht70WYxtb4+gbHAI9s01oLmB7Q9+/t55+Kty9Hsr2J6cOCVEKiAbRJwBG4JZQuAeO347+WkIsWe0vbC7R9hsDa8PBDophsRuL8GiTrHpYVd4s= 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=kYskL6EN; arc=none smtp.client-ip=209.85.214.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="kYskL6EN" Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-234f17910d8so26316605ad.3; Sat, 19 Jul 2025 04:13:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752923597; x=1753528397; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=bvKCv6POf+5AvCdbHnruXesLTDDbd3C3Hwxx7A1XmSQ=; b=kYskL6ENc4OPaXs1xNRtoE5LauuVd7Cx3AB5w0BirHEbhDoP67OZQ7sOm2PPBe7UOa fnnkjwd2Ss9AsUqTbRS+pK+siR43rHAQUzQ6YHmb1Xpjus52ZOPgCVBU3+Va1fELhg5d OrVMArms1ZlrA7CgCY9mOoMJRBbzkSHALRYswH7Agggvy84NtZon1895l8VmDkkSCJO0 JEGuFRVo2DNrm0L8pHBtMHdDUaiOlsgSTm7F+JHZre5oAa2U+m5xWQ48+CWCKsj3dJ+h /B4MvmgbI5AxtkarWORj+00PWLqzucSZznmFMBU9kOSzafpdUoORAFrcUJuC4z7Oowac 5/zg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752923597; x=1753528397; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=bvKCv6POf+5AvCdbHnruXesLTDDbd3C3Hwxx7A1XmSQ=; b=UyRP9h6nuaDxFw7eGyfCypFkjsuoiSdjySiS/EdZesqIZv5YktDcrAa2KsgNsI7apU Pm70EfdpNBEkZuWnllIV0ocumvkPjOM/18hnDT9s/sqgNzU7lcdpcBznCeDVBUNPAr/1 p9ZgBS97O82SA63yzhnBqpteHU37xvnK2SZdETMBOHM9+fwmE9WO9oYOd8RCRJ2nw1D5 Dkx+FmfQcJyRpPGJz8QJMtaj69/S2uwKAQnK3+aXoumV01e0lGASLsTl2elnQo1SDAVp 8NQs+UpxByq8cRwI18myG5tQE1Mi4hP2p7+JLRqTXftrAh5FlR+kdL+04P1ezmti4QJG 4X7A== X-Forwarded-Encrypted: i=1; AJvYcCVnlQptGWWHiOTFe24ElGNfHS2JKvTeDlsHmRUWMeG8W/cB/wODbdE6IGWn7WM82Ko7MISFmAuyyUTytNNNBCj1@vger.kernel.org, AJvYcCXggJQHIFD5GtMnLIthrZ0hoScGePRgxQMvXtSxLYIERuLy0N564y1JGTqgXD26fxKQwqW7BNHVcYBbLVo=@vger.kernel.org X-Gm-Message-State: AOJu0YwufS+1+kltl+LeTUjHrJBRQ/CQ0j+liutUQbx/ZpZg5Of9HTzv 0fYcWuUf6cgddtFoWDz4Ukqp30Jx7LdBGr2Zbw0hqhUNi8pY3YW0UDuQ X-Gm-Gg: ASbGncuIzGR6jhzR8Wf17m6bq365rMoPVAHbvegld/bMADKwEcY6QGln+8o/lFNw3jF m3otVfoGzKzoYceReQwrT8jY67ooWQBWZJEdKHIvAW8f9GA4WPdKvZj/AnBtxAlRmeZkweTjVRQ uatVO7JVHhnVAhV+8kYHcROqHzXaewOIdbFWBtC55cmcXXnuOsWmhXbWaqmsWSOli99FC/Qr/Tk UIXxVBI9svD50mYweQS92HhhUUX8mZvE1jjGHEpmQs+nkx21JHibHf7fs6cX7xq0qZcwl7pZXlN CPUwstKO+RkxKxbOZdPAcpKPhkqk3HAGLU0GdQ5gUsS8SO8FvwwwpgEb/4fZMByrtTyabz/kVEu uVr3eUB3THhYshSOc6vqO X-Google-Smtp-Source: AGHT+IFRfrvqzxiwMbV8CwJ5I6js0u8MPmEPzO2Ffha8E3FwP+dU4AjKqVbLuCunSymTwhiOfQz7sw== X-Received: by 2002:a17:902:e542:b0:234:c549:d9dd with SMTP id d9443c01a7336-23e3b8832damr89104455ad.48.1752923596740; Sat, 19 Jul 2025 04:13:16 -0700 (PDT) Received: from [0.0.5.57] ([136.159.213.146]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-23e3b6b4c81sm27388875ad.114.2025.07.19.04.13.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 19 Jul 2025 04:13:16 -0700 (PDT) From: Abhinav Saxena Date: Sat, 19 Jul 2025 05:13:12 -0600 Subject: [PATCH RFC 2/4] landlock: implement memfd detection 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 Message-Id: <20250719-memfd-exec-v1-2-0ef7feba5821@gmail.com> References: <20250719-memfd-exec-v1-0-0ef7feba5821@gmail.com> In-Reply-To: <20250719-memfd-exec-v1-0-0ef7feba5821@gmail.com> To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G=C3=BCnther_Noack?= , Paul Moore , James Morris , "Serge E. Hallyn" , Shuah Khan , Nathan Chancellor , Nick Desaulniers , Bill Wendling , Justin Stitt Cc: linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Abhinav Saxena X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1752923593; l=13773; i=xandfury@gmail.com; s=20250614; h=from:subject:message-id; bh=jTePs6g/xDy0KJam8dPT5XZrMR7zmL6IWyOVHqVv93U=; b=HlbWfRKoqOSYdiRe79uit+bXuea7BKOQbxcPW3mT3KSlkWnzsPb+SCiKphyYqRGAlS2ET9gL4 FOJgOQJksbiDbI62fqDQAL0U7GOXpvua/JcHEmHQDHc6XN3IXtg+aBX X-Developer-Key: i=xandfury@gmail.com; a=ed25519; pk=YN6w7WNet8skqvMWxhG5BlAmtd1SQmo8If6Mofh4k44= Add is_memfd_file() function to reliably detect memfd files by checking for "memfd:" prefix in dentry names on shmem-backed files. This distinguishes true memfd files from regular shmem files. Move domain_is_scoped() to domain.c for reuse across subsystems. Add comprehensive kunit tests for memfd detection edge cases. Signed-off-by: Abhinav Saxena --- security/landlock/domain.c | 67 +++++++++++++++ security/landlock/domain.h | 4 + security/landlock/fs.c | 210 +++++++++++++++++++++++++++++++++++++++++= ++++ security/landlock/task.c | 67 --------------- 4 files changed, 281 insertions(+), 67 deletions(-) diff --git a/security/landlock/domain.c b/security/landlock/domain.c index a647b68e8d06..993c299ca263 100644 --- a/security/landlock/domain.c +++ b/security/landlock/domain.c @@ -262,3 +262,70 @@ kunit_test_suite(test_suite); #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ =20 #endif /* CONFIG_AUDIT */ + +/** + * domain_is_scoped - Checks if the client domain is scoped in the same + * domain as the server. + * + * @client: IPC sender domain. + * @server: IPC receiver domain. + * @scope: The scope restriction criteria. + * + * Returns: True if the @client domain is scoped to access the @server, + * unless the @server is also scoped in the same domain as @client. + */ +bool domain_is_scoped(const struct landlock_ruleset *const client, + const struct landlock_ruleset *const server, + access_mask_t scope) +{ + int client_layer, server_layer; + const struct landlock_hierarchy *client_walker, *server_walker; + + /* Quick return if client has no domain */ + if (WARN_ON_ONCE(!client)) + return false; + + client_layer =3D client->num_layers - 1; + client_walker =3D client->hierarchy; + /* + * client_layer must be a signed integer with greater capacity + * than client->num_layers to ensure the following loop stops. + */ + BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers)); + + server_layer =3D server ? (server->num_layers - 1) : -1; + server_walker =3D server ? server->hierarchy : NULL; + + /* + * Walks client's parent domains down to the same hierarchy level + * as the server's domain, and checks that none of these client's + * parent domains are scoped. + */ + for (; client_layer > server_layer; client_layer--) { + if (landlock_get_scope_mask(client, client_layer) & scope) + return true; + + client_walker =3D client_walker->parent; + } + /* + * Walks server's parent domains down to the same hierarchy level as + * the client's domain. + */ + for (; server_layer > client_layer; server_layer--) + server_walker =3D server_walker->parent; + + for (; client_layer >=3D 0; client_layer--) { + if (landlock_get_scope_mask(client, client_layer) & scope) { + /* + * Client and server are at the same level in the + * hierarchy. If the client is scoped, the request is + * only allowed if this domain is also a server's + * ancestor. + */ + return server_walker !=3D client_walker; + } + client_walker =3D client_walker->parent; + server_walker =3D server_walker->parent; + } + return false; +} diff --git a/security/landlock/domain.h b/security/landlock/domain.h index 7fb70b25f85a..21a9eea644bd 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -171,4 +171,8 @@ static inline void landlock_put_hierarchy(struct landlo= ck_hierarchy *hierarchy) } } =20 +bool domain_is_scoped(const struct landlock_ruleset *const client, + const struct landlock_ruleset *const server, + access_mask_t scope); + #endif /* _SECURITY_LANDLOCK_DOMAIN_H */ diff --git a/security/landlock/fs.c b/security/landlock/fs.c index da862fda329b..d86d21034f4c 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -25,19 +26,26 @@ #include #include #include +#include +#include #include #include #include #include #include #include +#include #include #include +#include +#include #include #include #include +#include #include #include +#include =20 #include "access.h" #include "audit.h" @@ -1667,6 +1675,201 @@ get_required_file_open_access(const struct file *co= nst file) return access; } =20 +/** + * is_memfd_file - Check if file was created via memfd_create() + * @file: File to check + * + * Returns true if @file was created via memfd_create(), false otherwise. + * + * memfd files are shmem-backed files with "memfd:" prefix in their dentry= name. + * This is the definitive way to distinguish memfd files from regular shmem + * files. + */ +static bool is_memfd_file(struct file *file) +{ + const struct dentry *dentry; + const unsigned char *name; + size_t name_len; + + /* Fast path: basic validation */ + if (unlikely(!file)) + return false; + + /* Must be shmem-backed first - this is the cheapest definitive check */ + if (!shmem_file(file)) + return false; + +#ifdef CONFIG_MEMFD_CREATE + + /* Validate dentry and get name info */ + dentry =3D file->f_path.dentry; + if (unlikely(!dentry)) + return false; + + name_len =3D dentry->d_name.len; + name =3D dentry->d_name.name; + + /* memfd files always have "memfd:" prefix (6 characters) */ + if (name_len < 6 || unlikely(!name)) + return false; + + /* Check for exact "memfd:" prefix */ + return memcmp(name, "memfd:", 6) =3D=3D 0; +#else + return false; +#endif +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +/* + * Test that is_memfd_file() returns false for NULL input + */ +static void test_memfd_null_file(struct kunit *test) +{ + KUNIT_EXPECT_FALSE(test, is_memfd_file(NULL)); +} + +/* + * Test that regular shmem files are correctly distinguished from memfd fi= les + */ +static void test_shmem_vs_memfd_detection(struct kunit *test) +{ + struct file *shmem_files[4]; + static const char *const names[] =3D { + "regular_shmem", "", "large_shmem", + "memfd_fake" /* This should NOT be detected as memfd */ + }; + static const size_t sizes[] =3D { 4096, 4096, 1024 * 1024, 8192 }; + static const unsigned long vm_flags[] =3D { VM_NORESERVE, 0, + VM_NORESERVE | VM_ACCOUNT, + VM_NORESERVE }; + int i; + + for (i =3D 0; i < ARRAY_SIZE(shmem_files); i++) { + shmem_files[i] =3D + shmem_file_setup(names[i], sizes[i], vm_flags[i]); + KUNIT_ASSERT_FALSE(test, IS_ERR(shmem_files[i])); + + /* All should be shmem-backed but NOT memfd */ + KUNIT_EXPECT_TRUE(test, shmem_file(shmem_files[i])); + KUNIT_EXPECT_FALSE(test, is_memfd_file(shmem_files[i])); + + /* Verify dentry name doesn't have memfd: prefix */ + if (shmem_files[i]->f_path.dentry && + shmem_files[i]->f_path.dentry->d_name.name) { + const char *dentry_name =3D + shmem_files[i]->f_path.dentry->d_name.name; + KUNIT_EXPECT_TRUE(test, + strlen(dentry_name) < 6 || + memcmp(dentry_name, + "memfd:", 6) !=3D 0); + } + + fput(shmem_files[i]); + } +} + +/* + * Test edge cases and boundary conditions + */ +static void test_memfd_detection_edge_cases(struct kunit *test) +{ + struct file *edge_case_files[3]; + static const char *const tricky_names[] =3D { + "memf", /* Too short for memfd: prefix */ + "memfd", /* Still too short */ + "memfdx:test" /* Wrong prefix */ + }; + int i; + + for (i =3D 0; i < ARRAY_SIZE(edge_case_files); i++) { + edge_case_files[i] =3D + shmem_file_setup(tricky_names[i], 4096, VM_NORESERVE); + KUNIT_ASSERT_FALSE(test, IS_ERR(edge_case_files[i])); + + /* All should be shmem but NOT memfd due to incorrect naming */ + KUNIT_EXPECT_TRUE(test, shmem_file(edge_case_files[i])); + KUNIT_EXPECT_FALSE(test, is_memfd_file(edge_case_files[i])); + + fput(edge_case_files[i]); + } +} + +/* + * Test detection consistency across multiple calls + */ +static void test_memfd_detection_consistency(struct kunit *test) +{ + struct file *file; + bool initial_result, subsequent_result; + int iteration; + + file =3D shmem_file_setup("consistency_test", 4096, VM_NORESERVE); + KUNIT_ASSERT_FALSE(test, IS_ERR(file)); + + /* Get initial detection result */ + initial_result =3D is_memfd_file(file); + + /* Multiple calls should return identical results */ + for (iteration =3D 0; iteration < 10; iteration++) { + subsequent_result =3D is_memfd_file(file); + KUNIT_EXPECT_EQ(test, initial_result, subsequent_result); + } + + fput(file); +} + +#ifdef CONFIG_MEMFD_CREATE + +/* + * Test performance characteristics (ensure function is reasonably fast) + */ +static void test_memfd_detection_performance(struct kunit *test) +{ + struct file *files[5]; + static const char *const names[] =3D { "perf1", "perf2", "perf3", "perf4", + "perf5" }; + ktime_t start_time, end_time; + s64 duration_ns; + int i, j; + const int iterations =3D 1000; + + /* Set up test files */ + for (i =3D 0; i < ARRAY_SIZE(files); i++) { + files[i] =3D shmem_file_setup(names[i], 4096, VM_NORESERVE); + KUNIT_ASSERT_FALSE(test, IS_ERR(files[i])); + } + + /* Time the detection function */ + start_time =3D ktime_get(); + + for (i =3D 0; i < iterations; i++) { + for (j =3D 0; j < ARRAY_SIZE(files); j++) { + bool result =3D is_memfd_file(files[j]); + (void)result; /* Suppress unused variable warning */ + } + } + + end_time =3D ktime_get(); + duration_ns =3D ktime_to_ns(ktime_sub(end_time, start_time)); + + /* Cleanup */ + for (i =3D 0; i < ARRAY_SIZE(files); i++) + fput(files[i]); + + /* Performance check: should complete in reasonable time */ + /* This is a sanity check - actual limits depend on system */ + KUNIT_EXPECT_LT(test, duration_ns, + 1000000000LL); /* Less than 1 second */ + + pr_info("memfd detection performance: %lld ns for %d iterations on %zu fi= les\n", + duration_ns, iterations, ARRAY_SIZE(files)); +} + +#endif /* CONFIG_MEMFD_CREATE */ +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + static int hook_file_alloc_security(struct file *const file) { /* @@ -1949,6 +2152,13 @@ static struct kunit_case test_cases[] =3D { KUNIT_CASE(test_is_eacces_with_none), KUNIT_CASE(test_is_eacces_with_refer), KUNIT_CASE(test_is_eacces_with_write), + KUNIT_CASE(test_memfd_null_file), + KUNIT_CASE(test_shmem_vs_memfd_detection), + KUNIT_CASE(test_memfd_detection_edge_cases), + KUNIT_CASE(test_memfd_detection_consistency), +#ifdef CONFIG_MEMFD_CREATE + KUNIT_CASE(test_memfd_detection_performance), +#endif {} }; /* clang-format on */ diff --git a/security/landlock/task.c b/security/landlock/task.c index 2385017418ca..559a96a97ab6 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -165,73 +165,6 @@ static int hook_ptrace_traceme(struct task_struct *con= st parent) return err; } =20 -/** - * domain_is_scoped - Checks if the client domain is scoped in the same - * domain as the server. - * - * @client: IPC sender domain. - * @server: IPC receiver domain. - * @scope: The scope restriction criteria. - * - * Returns: True if the @client domain is scoped to access the @server, - * unless the @server is also scoped in the same domain as @client. - */ -static bool domain_is_scoped(const struct landlock_ruleset *const client, - const struct landlock_ruleset *const server, - access_mask_t scope) -{ - int client_layer, server_layer; - const struct landlock_hierarchy *client_walker, *server_walker; - - /* Quick return if client has no domain */ - if (WARN_ON_ONCE(!client)) - return false; - - client_layer =3D client->num_layers - 1; - client_walker =3D client->hierarchy; - /* - * client_layer must be a signed integer with greater capacity - * than client->num_layers to ensure the following loop stops. - */ - BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers)); - - server_layer =3D server ? (server->num_layers - 1) : -1; - server_walker =3D server ? server->hierarchy : NULL; - - /* - * Walks client's parent domains down to the same hierarchy level - * as the server's domain, and checks that none of these client's - * parent domains are scoped. - */ - for (; client_layer > server_layer; client_layer--) { - if (landlock_get_scope_mask(client, client_layer) & scope) - return true; - - client_walker =3D client_walker->parent; - } - /* - * Walks server's parent domains down to the same hierarchy level as - * the client's domain. - */ - for (; server_layer > client_layer; server_layer--) - server_walker =3D server_walker->parent; - - for (; client_layer >=3D 0; client_layer--) { - if (landlock_get_scope_mask(client, client_layer) & scope) { - /* - * Client and server are at the same level in the - * hierarchy. If the client is scoped, the request is - * only allowed if this domain is also a server's - * ancestor. - */ - return server_walker !=3D client_walker; - } - client_walker =3D client_walker->parent; - server_walker =3D server_walker->parent; - } - return false; -} - static bool sock_is_scoped(struct sock *const other, const struct landlock_ruleset *const domain) { --=20 2.43.0 From nobody Mon Oct 6 15:12:20 2025 Received: from mail-pl1-f175.google.com (mail-pl1-f175.google.com [209.85.214.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1845B23A563; Sat, 19 Jul 2025 11:13:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752923600; cv=none; b=Tw2HF1JxYOIpmQJAZ7JKQYLaVunC8I9TyZhbSlFrDHkaesAurbqh1ApCMYoV4H631ZaJgZW2+UNlwM5I9ZH8yN5QhF37UiLgcWcNk2kVQKB+PrJyVT6Fs+QUGxK9J9O7zg8n6QFwAlWRn46kk/glv2quDftwlJWzeFph85DoD4Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752923600; c=relaxed/simple; bh=r8lTNjeNnUfemO2GypZqiyYgLF4NkQp7YFBIugfmea8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=cdTqZoAFUb4S34p1P5QTBiNVabwuFh0uKPgBXD1VlMTwu9OqC9moCX8zHzjzzGrxTIM+tiN5fH8taoWb5wS/hPd6MlwWj7MeRFyYJPiXgNiquVgw7dwxwyL/h/k8F3pCKhVWvPTR0kodUXarV8cxp26WnJY/Cpu106qF5FbIYMc= 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=kI0dpn3E; arc=none smtp.client-ip=209.85.214.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="kI0dpn3E" Received: by mail-pl1-f175.google.com with SMTP id d9443c01a7336-236470b2dceso23802995ad.0; Sat, 19 Jul 2025 04:13:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752923598; x=1753528398; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=5RTREgjI7+tNhJ+8DPCuoyFAFcZSAMYB6+g1NG2DmPY=; b=kI0dpn3E1yBiTvMwtyt+Hq+6PjEtVmlzVVHbPIf1n5cj+5TH2HotrSGHSDcMftPUAA chT1zlO6clLzNYZcClhgeqEh3eRgIaXJMr9lYQCpVYE5g60/ObEkRApDcir55fzwS1Ol lGgg5+kWfLz4tUKHrk3U9lxNyCmilW/NovNsBUpenB1X4CD2Eh3MpgkqsS6SmWt1AG7r LeAVT8gAnTTUplE7tqBRFOvJXW/yro5PDdZoSqRE8DEtCy8tfTzGmtSWnfP23naT840y teHn0N130YQnE1OEGRc6uNyS3C3z1lcjnFMMO8zlYULnOTkPnY0uqNdqsabHhEbibzNp MvkA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752923598; x=1753528398; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=5RTREgjI7+tNhJ+8DPCuoyFAFcZSAMYB6+g1NG2DmPY=; b=sgNkFyb+qb8Lk2x4qwF9a/tL/Knu1MmwkOwr6ie/iymIUeNXtDIxcm7T6HCaXVFtKh 24C/51r6ITzYeI/DKY5ST24wtSmmBrzlfb9obQTGgRS5NKa74s/1+gJ5ddvASEFF5foM 1sDF4TGQFiLKMmnfUviqmCgkmM4Ibx+22pdw5N10EVvVYlvs49Trc328XirPOsJ+4C0z XoQDBkRhpD2AyQEA5f1wrnZ89gXUCWkcY1RgUQmQsDDin3j8Jg7C2Ma5MHJ4SrjgR968 x+ZhJHjCMSGlahnQ/hf0GogrOAx10KCOJBo3xpBoeTYVSKAN33dkxWWWOfPunEKiBwX1 PQcw== X-Forwarded-Encrypted: i=1; AJvYcCU2grk361yeMJ+aJ7FClEzNYiv3xLPsiHUCLwlAmw9ZgGKyiYfGSHkOz2IYnGeAEGZOj5/2Np5vUfZ+nyw=@vger.kernel.org, AJvYcCWFUP/GmTA2Ju0sdr/Lux2Dt31zIqiAFTBd59R+dSLcgurPWGMi6+cUtY87EMmFBFj+WOtux+m7vtqDjSC1Gn7j@vger.kernel.org X-Gm-Message-State: AOJu0Yz2N3tB4Ge1/PZSXX1H9KjI39G0FKXPgNjyZKTDttU2CZonOEes Q8E4Mr4vi//9s/NsyLaB5Y5NhIne0uEQbJ4I+rZoHhfgzqOXg5fZssM9 X-Gm-Gg: ASbGnctDc6ebQGagsOwRjBpkOkOGecaEq98rAnkly/CLW14vY6z+dCfjsvVbx8/pjQV a1W1XWc4ffk82BLSWCji/HgJf9pylokH1LhyWIicabnuUKedXyNZy+jihHyy2WzXwq2cijr0/X4 mDwGrVWeJXximnsv+VbXM5epNBFgXpouQGV68/+b5Yy3+TKVuIKNemLzd/YTPiJUu3H9JQi7Bln CDOmu0YINJ5KdL985Wz0t+V4AqKOQ/qE0Udon9x6+kqViUYjTKJ2LQV+/XDehmfer7OXkK4RiVA ua155YY4Wp9xLMFtk5ccuEC03mch4pBei1ReFP3veodI1jzOCDUGAGGabuicmSa91nkU8YY0sA6 qqzDrcbNq/s1pP16Z5udC X-Google-Smtp-Source: AGHT+IHIGCLWVvv5yalOVOy8WKDgdJNrEc9C/gFLLDdC9BVMS/h/JNjAbdeoYEJ58i9QE8Z26itLxQ== X-Received: by 2002:a17:903:228d:b0:23c:8f4c:6658 with SMTP id d9443c01a7336-23e2572a0dcmr188760075ad.25.1752923598310; Sat, 19 Jul 2025 04:13:18 -0700 (PDT) Received: from [0.0.5.57] ([136.159.213.146]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-23e3b6b4c81sm27388875ad.114.2025.07.19.04.13.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 19 Jul 2025 04:13:17 -0700 (PDT) From: Abhinav Saxena Date: Sat, 19 Jul 2025 05:13:13 -0600 Subject: [PATCH RFC 3/4] landlock: add memfd exec LSM hooks and scoping 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 Message-Id: <20250719-memfd-exec-v1-3-0ef7feba5821@gmail.com> References: <20250719-memfd-exec-v1-0-0ef7feba5821@gmail.com> In-Reply-To: <20250719-memfd-exec-v1-0-0ef7feba5821@gmail.com> To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G=C3=BCnther_Noack?= , Paul Moore , James Morris , "Serge E. Hallyn" , Shuah Khan , Nathan Chancellor , Nick Desaulniers , Bill Wendling , Justin Stitt Cc: linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Abhinav Saxena X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1752923593; l=8635; i=xandfury@gmail.com; s=20250614; h=from:subject:message-id; bh=r8lTNjeNnUfemO2GypZqiyYgLF4NkQp7YFBIugfmea8=; b=8L4KmWE3ZWhZjyUmYKRhVFPDyBVShtvoNJz8LgLdOGrxWWSTRS3sKp+1bAgGYFP//GtdlcEDP NNLSFLkuXsQDn6+Pv0ANlAxG745Ahfj+4ht/oU7zkNMyd/AGK4sBf10 X-Developer-Key: i=xandfury@gmail.com; a=ed25519; pk=YN6w7WNet8skqvMWxhG5BlAmtd1SQmo8If6Mofh4k44= Implement LSM hooks to enforce memfd execution restrictions: - hook_mmap_file: Prevent executable mapping of memfd files - hook_file_mprotect: Block mprotect() adding PROT_EXEC to memfd mappings - hook_bprm_creds_for_exec: Prevent direct execution via execve() family - hook_file_alloc_security: Initialize memfd files with proper access masks All hooks use domain hierarchy checking to enforce scoped restrictions with proper audit logging. This prevents multiple attack vectors: - Direct mmap(PROT_EXEC) on memfd - Two-stage mmap(PROT_READ) + mprotect(PROT_EXEC) bypass - execve("/proc/self/fd/N") anonymous execution Implement memfd execution access control in check_memfd_execute_access() using hierarchy-aware domain checking Signed-off-by: Abhinav Saxena --- security/landlock/cred.c | 14 ---- security/landlock/fs.c | 195 +++++++++++++++++++++++++++++++++++++++++++= +++- 2 files changed, 194 insertions(+), 15 deletions(-) diff --git a/security/landlock/cred.c b/security/landlock/cred.c index 0cb3edde4d18..356dad0b7e9b 100644 --- a/security/landlock/cred.c +++ b/security/landlock/cred.c @@ -43,25 +43,11 @@ static void hook_cred_free(struct cred *const cred) landlock_put_ruleset_deferred(dom); } =20 -#ifdef CONFIG_AUDIT - -static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm) -{ - /* Resets for each execution. */ - landlock_cred(bprm->cred)->domain_exec =3D 0; - return 0; -} - -#endif /* CONFIG_AUDIT */ - static struct security_hook_list landlock_hooks[] __ro_after_init =3D { LSM_HOOK_INIT(cred_prepare, hook_cred_prepare), LSM_HOOK_INIT(cred_transfer, hook_cred_transfer), LSM_HOOK_INIT(cred_free, hook_cred_free), =20 -#ifdef CONFIG_AUDIT - LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec), -#endif /* CONFIG_AUDIT */ }; =20 __init void landlock_add_cred_hooks(void) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index d86d21034f4c..e8b58f2fd87e 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -1880,7 +1880,24 @@ static int hook_file_alloc_security(struct file *con= st file) * without going through the file_open hook, for example when using * memfd_create(2). */ - landlock_file(file)->allowed_access =3D LANDLOCK_MASK_ACCESS_FS; + access_mask_t allowed_access =3D LANDLOCK_MASK_ACCESS_FS; + const struct landlock_cred_security *subject; + size_t layer; + static const struct access_masks memfd_scope =3D { + .scope =3D LANDLOCK_SCOPE_MEMFD_EXEC, + }; + + /* allow everything by default */ + landlock_file(file)->allowed_access =3D allowed_access; + + subject =3D landlock_get_applicable_subject(current_cred(), memfd_scope, + &layer); + if (subject && is_memfd_file(file)) { + /* Creator domain restricts memfd execution */ + allowed_access &=3D ~LANDLOCK_ACCESS_FS_EXECUTE; + landlock_file(file)->allowed_access =3D allowed_access; + /* Store creator and audit... */ + } return 0; } =20 @@ -2107,6 +2124,178 @@ static void hook_file_free_security(struct file *fi= le) landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain); } =20 +static bool +check_memfd_execute_access(const struct file *file, + const struct landlock_cred_security **subject, + size_t *layer_plus_one) +{ + const struct landlock_ruleset *executor_domain, *creator_domain; + const struct landlock_cred_security *creator_subject; + static const struct access_masks memfd_scope =3D { + .scope =3D LANDLOCK_SCOPE_MEMFD_EXEC, + }; + size_t creator_layer_plus_one =3D 0; + bool executor_scoped, creator_scoped, is_scoped; + + *subject =3D NULL; + *layer_plus_one =3D 0; + + /* Check scoping status for both executor and creator */ + *subject =3D landlock_get_applicable_subject(current_cred(), memfd_scope, + layer_plus_one); + creator_subject =3D landlock_get_applicable_subject( + file->f_cred, memfd_scope, &creator_layer_plus_one); + + executor_scoped =3D (*subject !=3D NULL); + creator_scoped =3D (creator_subject !=3D NULL); + + if (!creator_scoped) + return true; /* No scoping enabled, allow execution */ + + /* Get domains for comparison */ + executor_domain =3D executor_scoped ? (*subject)->domain : NULL; + creator_domain =3D creator_scoped ? creator_subject->domain : + landlock_cred(file->f_cred)->domain; + + pr_info("MEMFD_DEBUG: executor_domain=3D%p, creator_domain=3D%p\n", + executor_domain, creator_domain); + + /* + * Same-domain: deny to prevent read-to-execute bypass + * This prevents processes from bypassing execute restrictions + * by creating memfd in the same domain + */ + if (executor_domain =3D=3D creator_domain) + return false; + + /* + * Cross-domain: use domain hierarchy checks to see if executor is + * scoped from creator domain_is_scoped() returns true when access + * should be DENIED + */ + if (executor_scoped || creator_scoped) { + is_scoped =3D domain_is_scoped(executor_domain, creator_domain, + LANDLOCK_SCOPE_MEMFD_EXEC); + pr_info("MEMFD_DEBUG: Cross-domain: is_scoped=3D%d, returning=3D%d\n", + is_scoped, !is_scoped); + /* Return true (allow) when NOT scoped, false (deny) when scoped */ + return !is_scoped; + } + + return true; +} + +static int hook_mmap_file(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + const struct landlock_cred_security *subject; + size_t layer_plus_one; + + /* Only check executable mappings */ + if (!(prot & PROT_EXEC)) + return 0; + + /* Only restrict memfd files */ + if (!is_memfd_file(file)) + return 0; + + /* Check if memfd execution is allowed */ + if (check_memfd_execute_access(file, &subject, &layer_plus_one)) + return 0; + + /* Log denial for audit */ + if (subject) { + landlock_log_denial(subject, &(struct landlock_request) { + .type =3D LANDLOCK_REQUEST_SCOPE_MEMFD_EXEC, + .audit =3D { + .type =3D LSM_AUDIT_DATA_ANONINODE, + .u.file =3D file, + }, + .layer_plus_one =3D layer_plus_one, + }); + } + + return -EACCES; +} + +static int hook_file_mprotect(struct vm_area_struct *vma, unsigned long re= qprot, + unsigned long prot) +{ + const struct landlock_cred_security *subject; + size_t layer_plus_one; + + /* Only check when adding execute permission */ + if (!(prot & PROT_EXEC)) + return 0; + + /* Must have a file backing the VMA */ + if (!vma || !vma->vm_file) + return 0; + + /* Only restrict memfd files */ + if (!is_memfd_file(vma->vm_file)) + return 0; + + /* Check if memfd execution is allowed */ + if (check_memfd_execute_access(vma->vm_file, &subject, &layer_plus_one)) + return 0; + + /* Log denial for audit */ + if (subject) { + landlock_log_denial(subject, &(struct landlock_request) { + .type =3D LANDLOCK_REQUEST_SCOPE_MEMFD_EXEC, + .audit =3D { + .type =3D LSM_AUDIT_DATA_ANONINODE, + .u.file =3D vma->vm_file, + }, + .layer_plus_one =3D layer_plus_one, + }); + } + + return -EACCES; +} + +static int hook_bprm_creds_for_exec(struct linux_binprm *bprm) +{ +#ifdef CONFIG_AUDIT + /* Resets for each execution. */ + landlock_cred(bprm->cred)->domain_exec =3D 0; +#endif /* CONFIG_AUDIT */ + + const struct landlock_cred_security *subject; + size_t layer_plus_one; + struct file *file; + + if (!bprm) + return 0; + + file =3D bprm->file; + if (!file) + return 0; + + /* Only restrict memfd files */ + if (!is_memfd_file(file)) + return 0; + + /* Check if memfd execution is allowed */ + if (check_memfd_execute_access(file, &subject, &layer_plus_one)) + return 0; + + /* Log denial for audit */ + if (subject) { + landlock_log_denial(subject, &(struct landlock_request) { + .type =3D LANDLOCK_REQUEST_SCOPE_MEMFD_EXEC, + .audit =3D { + .type =3D LSM_AUDIT_DATA_ANONINODE, + .u.file =3D file, + }, + .layer_plus_one =3D layer_plus_one, + }); + } + + return -EACCES; /* maybe we should return EPERM? */ +} + static struct security_hook_list landlock_hooks[] __ro_after_init =3D { LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu), =20 @@ -2133,6 +2322,10 @@ static struct security_hook_list landlock_hooks[] __= ro_after_init =3D { LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat), LSM_HOOK_INIT(file_set_fowner, hook_file_set_fowner), LSM_HOOK_INIT(file_free_security, hook_file_free_security), + + LSM_HOOK_INIT(mmap_file, hook_mmap_file), + LSM_HOOK_INIT(file_mprotect, hook_file_mprotect), + LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec), }; =20 __init void landlock_add_fs_hooks(void) --=20 2.43.0 From nobody Mon Oct 6 15:12:20 2025 Received: from mail-pl1-f178.google.com (mail-pl1-f178.google.com [209.85.214.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 77D9B23AE60; Sat, 19 Jul 2025 11:13:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752923602; cv=none; b=ZgmaG6PVqhgko9lDITS+SrU5GWmeEb2ZyE2UF3MOYvpppTcwdWFA3E1zsvZKlsh+8qI1Fw27CntqT9+b/Cpr3QyNpRxsf91Ppra7zNJF/WSbjIi11SYtQKdkjKchrV6QZweB5UfUAlDWI1pRcfp7asMb3OJYXm6i9sQNYKkTEbA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752923602; c=relaxed/simple; bh=Iww/UpGyLtrx1j10ruDTf8LZMrndDjrz8uY2/BP9cpc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uAIT/0oCL8473lAnk/Lkea+zh0jLFridSTWL2Tyhf5AcWHI7hUubW8A8gfMAgFRaeHlq0qC8ky9pAUZtHeUJvZyz7UuNvnziAY3gdIznR0dwBBZ8r52SbU26Bpx585ForPtKWP9Rj9gaC9PN9dD3s8U0VqQSfYSzkvGwbk6ovO8= 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=UGb0DUF2; arc=none smtp.client-ip=209.85.214.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="UGb0DUF2" Received: by mail-pl1-f178.google.com with SMTP id d9443c01a7336-2363616a1a6so24755525ad.3; Sat, 19 Jul 2025 04:13:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752923600; x=1753528400; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=3Jd/5d465A0jCumj6dQXUppGRMZZOxb6FPpLcIZ9JVE=; b=UGb0DUF2c37L0yQF4ikb0YxS/G7UIKZoJ2L1Mxewe6zMnB86DVISH4VBKZiHF7qaZ8 JB+LYNbCf19lYFN+CjH3Aq5oFR18Nx+kiLI0195NsK1dg5+ILeDkDj413ZorwM8yH7G+ PE6Ovx+zm4mLqsAawvOa5Ec/xuE8/ewAdsp3amUj8vciEPwsKHI1hd9A+nlCTe6ntLMm fjxYRHdVKVG5lYK+eST9sYspChDiXW7mP8sypVaSlz3ZJx46i+PKNMIqCnDPph3LSgw4 nxoOc4dz/9CiQsomqnwSpJYpsA8tXQY+Te5aEN46ixsqf1gbfu0PZyRGHd/VX1+ta8Me nDBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752923600; x=1753528400; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=3Jd/5d465A0jCumj6dQXUppGRMZZOxb6FPpLcIZ9JVE=; b=A/24311TQxEC9YFEuUGGc3KSn+azy8awiXzBuH26xOVZeZ/tIPCx6ciPsxWqjHKaJU lOoyN+sk2ZME3hWmuKz5HFj4SHTwrFKat1zLuvvTkDLrcY+jioj3vGodoKX75lMF/RIJ NhHpgL1FFDWR+3Y19gcg8MPxbxXz6XARAHg8DMNtYQx1c/QqLZVJ6GQvCS1tdFpVzOr7 iFJrP8pYSNj8yzOsP2ci2PfhCBiXwm32B7DkwxxCSo5Mw5KNE6ZI7z2leJF8dmNHFV+7 KmjYrWUfO2IecOOx+g4sU+dnM0C5gdh8bKo2pQ+m97LPd9VxqBElqCS3Oxiq0CtwK5je lePw== X-Forwarded-Encrypted: i=1; AJvYcCW0ImEOu805hWMnR13w2p31fM8hnjpcsZUdViXT1X8HB7bgPER0pxVG76hPCWsWuZof8EDbpsAWK3mDUy0XEVzq@vger.kernel.org, AJvYcCX3PxB1rVznfb9TuG0o1hu3q7fbBFfaJUSqqNbt6/d3ov1QDDF+q4sizxc4BqgG+bIuFgHKLpX7WBRbjCw=@vger.kernel.org X-Gm-Message-State: AOJu0YxzOMZ879sUa4yf0ZZ9nb707ecRLaz6dcgQEmZTyhEy8dPnXJY6 pCr2NG5mu/ocDYo9eZEWuz61FGQOud6GZzZdStw6GrNtF+5hqKBCc6sr X-Gm-Gg: ASbGncsmv98B1F0aZxgalY90ruS7j7ySJfiU4ZlzvQSH6DUid+aJEHHrb0zAiSzf8zd XRTkUDJQuojw7polfZOSuUASzjOSwaUMkGC/aevdd/TtZYomyCjt7WiWZHJo/uvE20MhnsDKoGc YGACiyvAYglCygL9eJQ0HxDGbU/izDU2cagPNrqmY9aFb4IURZzTO3IoHVNga5iNHwbMQfRMfGQ I0+znk+2hGHHq4Oi9jpXnL/n/Wocf7JratTgjHrcA2fEd365jbpddbBz1BlnzMfqdlh+01fkhC+ 0V/GwM4kaS8kUAcyKJ+crDpswm7E7PyBwccK3HsOHYkhGMcBJoEfLNcOuVnAkxN+f9yrivn7MSq zt78wHuyrDV0tYd8ZFkJJ X-Google-Smtp-Source: AGHT+IEAPjC4pj685n73OCXBGa3ulx7qROou2OgY1ptOxHHWKAYpNN7RhIDQ9S1zZEHJLglu5uD+8g== X-Received: by 2002:a17:902:c409:b0:234:b41e:37a4 with SMTP id d9443c01a7336-23e256848f5mr163928225ad.6.1752923599651; Sat, 19 Jul 2025 04:13:19 -0700 (PDT) Received: from [0.0.5.57] ([136.159.213.146]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-23e3b6b4c81sm27388875ad.114.2025.07.19.04.13.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 19 Jul 2025 04:13:19 -0700 (PDT) From: Abhinav Saxena Date: Sat, 19 Jul 2025 05:13:14 -0600 Subject: [PATCH RFC 4/4] selftests/landlock: add memfd execution tests 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 Message-Id: <20250719-memfd-exec-v1-4-0ef7feba5821@gmail.com> References: <20250719-memfd-exec-v1-0-0ef7feba5821@gmail.com> In-Reply-To: <20250719-memfd-exec-v1-0-0ef7feba5821@gmail.com> To: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , =?utf-8?q?G=C3=BCnther_Noack?= , Paul Moore , James Morris , "Serge E. Hallyn" , Shuah Khan , Nathan Chancellor , Nick Desaulniers , Bill Wendling , Justin Stitt Cc: linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, llvm@lists.linux.dev, Abhinav Saxena X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1752923593; l=10834; i=xandfury@gmail.com; s=20250614; h=from:subject:message-id; bh=Iww/UpGyLtrx1j10ruDTf8LZMrndDjrz8uY2/BP9cpc=; b=kIjDOTlzaXRksQJMV4lD0AKnhdTH0vFXs450vzHO9XXX4DPVtJbviuK8aZKqG2ckFvORf7mmK 3sV2DwdEZCPBwGjmRnqKTUmFMhGYKAjUyCwn8jwjUyLmr7pbp67hTD2 X-Developer-Key: i=xandfury@gmail.com; a=ed25519; pk=YN6w7WNet8skqvMWxhG5BlAmtd1SQmo8If6Mofh4k44= Add core test suite for LANDLOCK_SCOPE_MEMFD_EXEC covering: - Same-domain execution restriction (prevent read-to-execute bypass) - execve() family syscall restrictions via /proc/self/fd/ path - Regular filesystem files remain unaffected by memfd scoping Tests validate that memfd execution restrictions are properly enforced while ensuring surgical targeting that doesn't impact legitimate file operations. Covers key attack vectors including anonymous execution and W^X policy bypass attempts. Signed-off-by: Abhinav Saxena --- .../selftests/landlock/scoped_memfd_exec_test.c | 325 +++++++++++++++++= ++++ 1 file changed, 325 insertions(+) diff --git a/tools/testing/selftests/landlock/scoped_memfd_exec_test.c b/to= ols/testing/selftests/landlock/scoped_memfd_exec_test.c new file mode 100644 index 000000000000..2513a44d8320 --- /dev/null +++ b/tools/testing/selftests/landlock/scoped_memfd_exec_test.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Landlock tests for LANDLOCK_SCOPE_MEMFD_EXEC domain restrictions + * + * These tests validate Landlock's hierarchical execution control for memfd + * objects. The scoping mechanism prevents processes from executing memfd + * created in different domain contexts. + * + * Copyright =C2=A9 2025 Abhinav Saxena + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "scoped_common.h" + +static int create_test_memfd(struct __test_metadata *const _metadata) +{ + int memfd; + static const char test_data[] =3D "#!/bin/sh\nexit 42\n"; + + memfd =3D memfd_create("test_exec", 0); + ASSERT_LE(0, memfd) + { + TH_LOG("Failed to create memfd: %s", strerror(errno)); + } + + ASSERT_EQ(fchmod(memfd, 0700), 0); + + ASSERT_EQ(0, ftruncate(memfd, sizeof(test_data))); + ASSERT_EQ(sizeof(test_data), + write(memfd, test_data, sizeof(test_data))); + ASSERT_EQ(0, lseek(memfd, 0, SEEK_SET)); + + return memfd; +} + +static bool test_mmap_exec_restriction(int memfd, bool expect_denied) +{ + void *addr; + const size_t page_size =3D getpagesize(); + + addr =3D mmap(NULL, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, memfd, + 0); + + if (expect_denied) { + bool correctly_denied =3D (addr =3D=3D MAP_FAILED && errno =3D=3D EACCES= ); + + if (addr !=3D MAP_FAILED) + munmap(addr, page_size); + return correctly_denied; + } + + if (addr =3D=3D MAP_FAILED) + return false; + + munmap(addr, page_size); + return true; +} + +/* clang-format off */ +FIXTURE(scoped_domains) {}; +/* clang-format on */ + +#include "scoped_base_variants.h" + +FIXTURE_SETUP(scoped_domains) +{ + drop_caps(_metadata); +} + +FIXTURE_TEARDOWN(scoped_domains) +{ +} + +/* + * Test that regular filesystem files are unaffected by memfd restrictions + * + * This test ensures that LANDLOCK_SCOPE_MEMFD_EXEC scoping only affects + * memfd objects and does not interfere with normal file execution or + * memory mapping of regular filesystem files. + * + * Security scenarios tested: + * - Scope isolation: memfd restrictions don't affect regular files + * - Proper targeting: only anonymous memory objects are restricted + * + * Scenarios considered (while allowing legitimate use): + * - Malicious process creates executable memfd -> BLOCKED + * - Same process maps legitimate executable file ->ALLOWED + * - Ensures restrictions are surgical, not broad + * + * Test flow: + * 1. Parent optionally creates scoped domain + * 2. Parent forks child process + * 3. Child optionally creates scoped domain + * 4. Child creates regular temporary file with executable content + * 5. Child creates memfd with same content + * 6. Test memfd execution ->should follow scoping rules + * 7. Test regular file execution ->should always work regardless of memfd + * scoping + * 8. Verify differential behavior confirms proper targeting + */ +TEST_F(scoped_domains, regular_file_unaffected) +{ + int tmp_fd, memfd; + char tmp_path[] =3D "/tmp/landlock_test_XXXXXX"; + void *addr; + const size_t page_size =3D getpagesize(); + bool memfd_should_be_denied; + + memfd_should_be_denied =3D variant->domain_child || + variant->domain_parent; + + if (variant->domain_parent) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_MEMFD_EXEC); + + pid_t child =3D fork(); + + ASSERT_LE(0, child); + + if (child =3D=3D 0) { + /* Child process */ + if (variant->domain_child) + create_scoped_domain(_metadata, + LANDLOCK_SCOPE_MEMFD_EXEC); + + /* Create regular file with executable test content */ + tmp_fd =3D mkstemp(tmp_path); + ASSERT_LE(0, tmp_fd); + ASSERT_EQ(0, fchmod(tmp_fd, 0755)); + + static const char test_data[] =3D "#!/bin/sh\nexit 42\n"; + + ASSERT_EQ(sizeof(test_data), + write(tmp_fd, test_data, sizeof(test_data))); + ASSERT_EQ(0, lseek(tmp_fd, 0, SEEK_SET)); + + /* Create memfd with identical content for comparison */ + memfd =3D create_test_memfd(_metadata); + + /* Test memfd execution - should follow scoping restrictions */ + bool memfd_correctly_handled =3D test_mmap_exec_restriction( + memfd, memfd_should_be_denied); + EXPECT_TRUE(memfd_correctly_handled); + + /* + * Test regular file execution - should always work regardless + * of memfd scoping + */ + addr =3D mmap(NULL, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, + tmp_fd, 0); + EXPECT_NE(MAP_FAILED, addr); + if (addr !=3D MAP_FAILED) + munmap(addr, page_size); + + /* Cleanup */ + close(memfd); + close(tmp_fd); + unlink(tmp_path); + _exit(_metadata->exit_code); + } + + /* Parent waits for child */ + int status; + + ASSERT_EQ(child, waitpid(child, &status, 0)); + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) !=3D EXIT_SUCCESS) + _metadata->exit_code =3D KSFT_FAIL; +} + +/* + * Test execve() family syscall restrictions on memfd + * + * This test validates that direct execution of memfd files via execve(), + * execveat(), and fexecve() syscalls is properly blocked when domain + * scoping is enabled. Tests the /proc/self/fd/ execution path commonly + * used for anonymous execution. + * + * Security scenarios tested: + * - Direct memfd execution via /proc/self/fd/ path + * - Anonymous execution prevention + * - execve() hook integration with memfd scoping + * + * Attack scenarios prevented: + * 1. execve("/proc/self/fd/N") where N is memfd file descriptor + * 2. execveat(memfd_fd, "", args, env, AT_EMPTY_PATH) - anonymous executi= on + * 3. fexecve(memfd_fd, args, env) - file descriptor execution + * + * Test flow: + * 1. Parent optionally creates scoped domain + * 2. Parent forks child process + * 3. Child optionally creates scoped domain + * 4. Child creates memfd with executable script content + * 5. Child attempts execve() using /proc/self/fd/N path + * 6. Verify: EACCES if scoped, successful execution (exit 42) if not scop= ed + * 7. Parent checks child exit status to determine success/failure + */ +TEST_F(scoped_domains, execve_restriction) +{ + int memfd; + char fd_path[64]; + bool should_be_denied; + + should_be_denied =3D variant->domain_child || variant->domain_parent; + TH_LOG("execve_restriction: parent=3D%d, child=3D%d\n", + variant->domain_parent, variant->domain_child); + + if (variant->domain_parent) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_MEMFD_EXEC); + + pid_t child =3D fork(); + + ASSERT_LE(0, child); + + if (child =3D=3D 0) { + /* Child process */ + if (variant->domain_child) { + create_scoped_domain(_metadata, + LANDLOCK_SCOPE_MEMFD_EXEC); + } + + memfd =3D create_test_memfd(_metadata); + snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", memfd); + + /* Attempt execve on memfd via /proc/self/fd/ path */ + char *const argv[] =3D { "test", NULL }; + char *const envp[] =3D { NULL }; + + int ret =3D execve(fd_path, argv, envp); + + ASSERT_EQ(-1, ret); + + /* If we reach here, execve failed */ + if (should_be_denied) { + EXPECT_EQ(EACCES, + errno); /* Should be blocked by Landlock */ + } else { + /* execve should have succeeded but failed for other reason */ + TH_LOG("execve failed unexpectedly: %s", + strerror(errno)); + } + + close(memfd); + _exit(_metadata->exit_code); + } + + /* Parent waits for child and checks exit status */ + int status; + + ASSERT_EQ(child, waitpid(child, &status, 0)); + + if (should_be_denied) { + /* Child should exit normally after execve was blocked */ + EXPECT_TRUE(WIFEXITED(status)); + } else { + /* + * Child should have executed successfully with script's + * exit code + */ + EXPECT_TRUE(WIFEXITED(status)); + EXPECT_EQ(42, + WEXITSTATUS(status)); /* Exit code from test script */ + } +} + +/* + * Test same-domain execution restriction (should always be denied when sc= oped) + * + * This test validates the "Same domain: DENY" rule from the security matr= ix. + * When a process is in a scoped domain, it should not be able to execute + * memfd objects that it created itself, preventing read-to-execute bypass. + * + * Security scenarios tested: + * - Read-to-execute bypass prevention within same domain + * - Self-execution blocking for memfd objects + * + * Attack scenario prevented: + * - Attacker process creates writable memfd in current domain + * - Writes malicious shellcode to the memfd via write() syscalls + * - Attempts to execute the same memfd via mmap(PROT_EXEC) + * - Should be BLOCKED by same-domain denial rule + * - Prevents bypassing W^X policies via anonymous memory + * + * Test flow: + * 1. Process optionally creates scoped domain + * 2. Process creates memfd (inherits current domain context) + * 3. Process attempts to mmap its own memfd with PROT_EXEC + * 4. Verify: ALLOW if not scoped, DENY if scoped (same domain rule) + */ +TEST_F(scoped_domains, same_domain_restriction) +{ + int memfd; + bool should_be_denied; + + /* Same domain should be denied when scoped, allowed when not scoped */ + should_be_denied =3D variant->domain_parent; + + if (variant->domain_parent) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_MEMFD_EXEC); + + /* Process creates and tries to execute its own memfd (same domain) */ + memfd =3D create_test_memfd(_metadata); + + bool test_passed =3D test_mmap_exec_restriction(memfd, should_be_denied); + + EXPECT_TRUE(test_passed); + + close(memfd); +} + +TEST_HARNESS_MAIN --=20 2.43.0