Linux Documentation
 help / color / mirror / Atom feed
* Re: [PATCH v2] Fail the build on RUST=y and RUST_IS_AVAILABLE=n
From: Andreas Hindborg @ 2026-05-28 11:54 UTC (permalink / raw)
  To: Sasha Finkelstein, Alice Ryhl, Benno Lossin, Björn Roy Baron,
	Boqun Feng, Danilo Krummrich, Gary Guo, Jonathan Corbet,
	Miguel Ojeda, Shuah Khan, Trevor Gross
  Cc: Neal Gompa, linux-doc, linux-kernel, rust-for-linux,
	Sasha Finkelstein
In-Reply-To: <20260521-evolve-to-crab-v2-1-c18e0e98fc54@chaosmail.tech>

"Sasha Finkelstein" <k@chaosmail.tech> writes:

> The current approach of silently disabling all rust drivers if the
> toolchain is missing results in users that try to compile their own
> kernels getting a "successful" build and then being confused about where
> did their drivers go. In comparison, missing openssl results in a build
> failure, not a disappearance of everything that depends on it.
>
> This also means that allyesconfig will depend on rust, but since the
> rust experiment concluded with "rust is here to stay", i believe that
> allyesconfig should be building rust drivers too.
>
> Signed-off-by: Sasha Finkelstein <k@chaosmail.tech>

This is long overdue.

Reviewed-by: Andreas Hindborg <a.hindborg@kernel.org>

Best regards,
Andreas Hindborg




^ permalink raw reply

* Re: [PATCH v4 6/6] arm64: hw_breakpoint: Enable FEAT_Debugv8p9
From: Will Deacon @ 2026-05-28 11:05 UTC (permalink / raw)
  To: Rob Herring (Arm)
  Cc: Mark Rutland, Catalin Marinas, Jonathan Corbet, Shuah Khan,
	Anshuman Khandual, linux-arm-kernel, linux-perf-users,
	linux-kernel, linux-doc, maz
In-Reply-To: <20260407-arm-debug-8-9-v4-6-a4864e69b0ea@kernel.org>

On Tue, Apr 07, 2026 at 09:29:48AM -0500, Rob Herring (Arm) wrote:
> From: Anshuman Khandual <anshuman.khandual@arm.com>
> 
> Currently, there can be maximum 16 breakpoints and 16 watchpoints available
> on a given platform - as detected from ID_AA64DFR0_EL1.[BRPs|WRPs] register
> fields. These breakpoints and watchpoints can be extended further up to
> 64 via a new arch feature FEAT_Debugv8p9.
> 
> Checking for FEAT_Debugv8p9 alone is not enough to enable the support.
> It is also necessary to determine if there are more than 16 breakpoints
> or watchpoints. The behavior with FEAT_Debugv8p9 and <=16 breakpoints
> and watchpoints is IMPDEF.
> 
> The addition of the MDSELR_EL1 to set the bank index makes the register
> accesses non-atomic. However, the combination of all the breakpoint code
> being in the kprobe blacklist and breakpoint install/uninstall being
> protected by perf locking (IRQs disabled and context lock) will prevent
> debug exceptions during accesses and serialize the accesses.
> 
> Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
> Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
> ---
> v4:
>  - Update commit message.
>  - Configure MDSCR_EL1_EMBWE on CPU reset/hotplug instead of every time
>    breakpoints are enabled/disabled.
>  - Drop unnecessary IRQ save and restore on register accesses.
>  - Stash checking whether FEAT_Debugv8p9 is used rather than reading
>    feature register on every register access.
>  - Check that we're greater than or equal to Debug_v8p9 not just equal
>    to.
>  - Use is_debug_v8p9_enabled() in get_num_brps/get_num_wrps(). Handle
>    the case when FEAT_Debugv8p9 is present, but the number of BP/WP
>    are <16. It is IMPDEF if ID_AA64DFR1_EL1 is used in this case. It is
>    also IMPDEF if MDSELR_EL1 is accessible. TF-A doesn't enable access
>    to MDSELR_EL1 in this case.
>  - Mark register access functions nokprobe.
> ---
>  arch/arm64/include/asm/hw_breakpoint.h | 47 ++++++++++++++++++++++++++--------
>  arch/arm64/kernel/debug-monitors.c     | 16 ++++++++----
>  arch/arm64/kernel/hw_breakpoint.c      | 41 +++++++++++++++++++++++++++--
>  3 files changed, 87 insertions(+), 17 deletions(-)

[...]

> @@ -138,19 +147,37 @@ static inline void ptrace_hw_copy_thread(struct task_struct *task)
>  /* Determine number of BRP registers available. */
>  static inline int get_num_brps(void)
>  {
> -	u64 dfr0 = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1);
> -	return 1 +
> -		cpuid_feature_extract_unsigned_field(dfr0,
> -						ID_AA64DFR0_EL1_BRPs_SHIFT);
> +	u64 dfr0, dfr1;
> +	int brps;
> +
> +	dfr0 = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1);
> +	brps = cpuid_feature_extract_unsigned_field(dfr0, ID_AA64DFR0_EL1_BRPs_SHIFT);
> +	if (is_debug_v8p9_enabled() && brps == 15) {
> +		dfr1 = read_sanitised_ftr_reg(SYS_ID_AA64DFR1_EL1);
> +		brps = cpuid_feature_extract_unsigned_field_width(dfr1,
> +								  ID_AA64DFR1_EL1_BRPs_SHIFT, 8);
> +		if (!brps)
> +			return 16;
> +	}
> +	return 1 + brps;
>  }
>  
>  /* Determine number of WRP registers available. */
>  static inline int get_num_wrps(void)
>  {
> -	u64 dfr0 = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1);
> -	return 1 +
> -		cpuid_feature_extract_unsigned_field(dfr0,
> -						ID_AA64DFR0_EL1_WRPs_SHIFT);
> +	u64 dfr0, dfr1;
> +	int wrps;
> +
> +	dfr0 = read_sanitised_ftr_reg(SYS_ID_AA64DFR0_EL1);
> +	wrps = cpuid_feature_extract_unsigned_field(dfr0, ID_AA64DFR0_EL1_WRPs_SHIFT);
> +	if (is_debug_v8p9_enabled() && wrps == 15) {
> +		dfr1 = read_sanitised_ftr_reg(SYS_ID_AA64DFR1_EL1);
> +		wrps = cpuid_feature_extract_unsigned_field_width(dfr1,
> +								  ID_AA64DFR1_EL1_WRPs_SHIFT, 8);
> +		if (!wrps)
> +			return 16;
> +	}
> +	return 1 + wrps;
>  }

[...]

> @@ -990,6 +1024,7 @@ static int __init arch_hw_breakpoint_init(void)
>  
>  	core_num_brps = get_num_brps();
>  	core_num_wrps = get_num_wrps();
> +	has_debug_v8p9 = (core_num_brps > 16) || (core_num_wrps > 16);

nit: FEAT_Debugv8p9 is advertised by ID_AA64DFR0_EL1.DebugVer and so
this should probably be called something else (e.g. 'has_register_banks').

Have you tested this in a guest? My reading is that MDCR_EL2.EMBWE will
be zero, but the ID registers can still advertise > 16 registers and
so I don't think this will work properly because writes to MDSELR_EL1
will either be trapped or ignored. It definitely feels like the KVM
piece of the puzzle is missing here and I think that it probably has to
be in place before we expose ID_AA64DFR1_EL1.

I'm also surprised not to see any ptrace changes in this series.
Specifically:

  1. I don't think we should expose any of this to compat tasks (see
     compat_ptrace_hbp_get_resource_info()) unless the 32-bit kernel is
     going to do that.

  2. 'struct user_hwdebug_state' retains a fixed length array of 16 for
     the debug regs and so the new registers aren't accessible in the
     REGSET_HW_{BREAK,WATCH} regsets. That means GDB can't use them and
     it also means they won't be included in coredumps iirc. I'm not sure
     whether we can safely extend the structure, so we might need to add
     some new ones...

Will

^ permalink raw reply

* Re: [RFC PATCH v1 00/13] exec: add spawn templates for repeated executable startup
From: Christian Brauner @ 2026-05-28 11:02 UTC (permalink / raw)
  To: Li Chen
  Cc: Kees Cook, Alexander Viro, linux-fsdevel, linux-api, linux-kernel,
	linux-mm, linux-arch, linux-doc, linux-kselftest, x86,
	Arnd Bergmann, Andy Lutomirski, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, H. Peter Anvin, Jan Kara,
	Jonathan Corbet, Shuah Khan
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

On Thu, May 28, 2026 at 05:52:21PM +0800, Li Chen wrote:
> Hi,
> 
> This is an early RFC for an idea that is probably still rough in both the
> UAPI and implementation details. Sorry for the rough edges; I am sending
> it now to check whether this direction is worth pursuing and to get
> feedback on the kernel/userspace boundary.

The idea of having a builder api for exec isn't all that crazy. But it
should simply be built on top of pidfds and thus pidfs itself instead.
It has all the basic infrastructure in place already. Any implementation
should also allow userspace to implement posix_spawn() on top of it.

fd = pidfd_open(0, PIDFD_EMPTY /* or better name */)

pidfd_config(fd, ...) // modeled similar to fsconfig()

^ permalink raw reply

* Re: [PATCH v4 5/6] arm64/boot: Enable EL2 requirements for FEAT_Debugv8p9
From: Will Deacon @ 2026-05-28 10:58 UTC (permalink / raw)
  To: Rob Herring (Arm)
  Cc: Mark Rutland, Catalin Marinas, Jonathan Corbet, Shuah Khan,
	Anshuman Khandual, linux-arm-kernel, linux-perf-users,
	linux-kernel, linux-doc, Marc Zyngier, kvmarm, Oliver Upton
In-Reply-To: <20260407-arm-debug-8-9-v4-5-a4864e69b0ea@kernel.org>

On Tue, Apr 07, 2026 at 09:29:47AM -0500, Rob Herring (Arm) wrote:
> From: Anshuman Khandual <anshuman.khandual@arm.com>
> 
> Fine grained trap control for MDSELR_EL1 register needs to be configured in
> HDFGRTR2_EL2, and HDFGWTR2_EL2 registers when kernel enters at EL1, but EL2
> is also present.
> 
> MDCR_EL2.EBWE needs to be enabled for additional (beyond 16) breakpoint and
> watchpoint exceptions when kernel enters at EL1, but EL2 is also present.
> 
> While here, also update booting.rst with MDCR_EL3 and SCR_EL3 requirements.
> 
> Cc: Marc Zyngier <maz@kernel.org>
> Cc: Oliver Upton <oliver.upton@linux.dev>
> Cc: kvmarm@lists.linux.dev
> Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
> Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
> ---
> v4:
>  - Add that the requirements only apply when there are >16
>    breakpoints/watchpoints
>  - Adapt to changes in v7.0-rc1
> ---
>  Documentation/arch/arm64/booting.rst | 13 +++++++++++++
>  arch/arm64/include/asm/el2_setup.h   | 14 ++++++++++++++
>  2 files changed, 27 insertions(+)
> 
> diff --git a/Documentation/arch/arm64/booting.rst b/Documentation/arch/arm64/booting.rst
> index 13ef311dace8..00ba91bbd278 100644
> --- a/Documentation/arch/arm64/booting.rst
> +++ b/Documentation/arch/arm64/booting.rst
> @@ -369,6 +369,19 @@ Before jumping into the kernel, the following conditions must be met:
>      - ZCR_EL2.LEN must be initialised to the same value for all CPUs the
>        kernel will execute on.
>  
> +  For CPUs with FEAT_Debugv8p9 extension present and >16 breakpoints or
> +  watchpoints:
> +
> +  - If the kernel is entered at EL1 and EL2 is present:
> +
> +    - HDFGRTR2_EL2.nMDSELR_EL1 (bit 5) must be initialized to 0b1
> +    - HDFGWTR2_EL2.nMDSELR_EL1 (bit 5) must be initialized to 0b1
> +    - MDCR_EL2.EBWE (bit 43) must be initialized to 0b1
> +
> +  - If EL3 is present:
> +
> +    - MDCR_EL3.EBWE (bit 43) must be initialized to 0b1
> +
>    For CPUs with the Scalable Matrix Extension (FEAT_SME):
>  
>    - If EL3 is present:
> diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h
> index 85f4c1615472..b51a280c18c0 100644
> --- a/arch/arm64/include/asm/el2_setup.h
> +++ b/arch/arm64/include/asm/el2_setup.h
> @@ -174,6 +174,13 @@
>  						// to own it.
>  
>  .Lskip_trace_\@:
> +	mrs	x1, id_aa64dfr0_el1
> +	ubfx	x1, x1, #ID_AA64DFR0_EL1_DebugVer_SHIFT, #4
> +	cmp	x1, #ID_AA64DFR0_EL1_DebugVer_V8P9
> +	b.lt	.Lskip_dbg_v8p9_\@

Why do you need to check the id register here?

> +
> +	orr	x2, x2, #MDCR_EL2_EBWE
> +.Lskip_dbg_v8p9_\@:
>  	msr	mdcr_el2, x2			// Configure debug traps
>  .endm
>  
> @@ -438,6 +445,13 @@
>  	orr	x0, x0, #HDFGRTR2_EL2_nPMSDSFR_EL1
>  
>  .Lskip_spefds_\@:
> +	mrs	x1, id_aa64dfr0_el1
> +	ubfx	x1, x1, #ID_AA64DFR0_EL1_DebugVer_SHIFT, #4
> +	cmp	x1, #ID_AA64DFR0_EL1_DebugVer_V8P9
> +	b.lt	.Lskip_dbg_v8p9_\@
> +
> +	mov_q   x0, HDFGWTR2_EL2_nMDSELR_EL1

Doesn't this clobber the trap configuration from the previous blocks?

Will

^ permalink raw reply

* Re: [PATCH v4 4/6] arm64/cpufeature: Add field details for ID_AA64DFR1_EL1 register
From: Will Deacon @ 2026-05-28 10:57 UTC (permalink / raw)
  To: Rob Herring (Arm)
  Cc: Mark Rutland, Catalin Marinas, Jonathan Corbet, Shuah Khan,
	Anshuman Khandual, linux-arm-kernel, linux-perf-users,
	linux-kernel, linux-doc, maz
In-Reply-To: <20260407-arm-debug-8-9-v4-4-a4864e69b0ea@kernel.org>

On Tue, Apr 07, 2026 at 09:29:46AM -0500, Rob Herring (Arm) wrote:
> From: Anshuman Khandual <anshuman.khandual@arm.com>
> 
> This adds required field details for ID_AA64DFR1_EL1, and also drops dummy
> ftr_raz[] array which is now redundant. These register fields will be used
> to enable increased breakpoint and watchpoint registers via FEAT_Debugv8p9
> later. The register fields have been marked as FTR_STRICT, unless there is
> a known variation in practice.
> 
> Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
> Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
> ---
>  arch/arm64/kernel/cpufeature.c | 21 ++++++++++++++++-----
>  1 file changed, 16 insertions(+), 5 deletions(-)
> 
> diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
> index c31f8e17732a..24c8e9147e35 100644
> --- a/arch/arm64/kernel/cpufeature.c
> +++ b/arch/arm64/kernel/cpufeature.c
> @@ -570,6 +570,21 @@ static const struct arm64_ftr_bits ftr_id_aa64dfr0[] = {
>  	ARM64_FTR_END,
>  };
>  
> +static const struct arm64_ftr_bits ftr_id_aa64dfr1[] = {
> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_ABL_CMPs_SHIFT, 8, 0),
> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_DPFZS_SHIFT, 4, 0),

Why does FTR_LOWER_SAFE make sense for DPFZS? From what I can tell, the
new behaviour isn't opt-in, so maybe an FTR_EXACT of 0 would make more
sense if we have to be non-strict (along with a comment like we have for
DFR0.PMUVer)?

> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_EBEP_SHIFT, 4, 0),
> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_ITE_SHIFT, 4, 0),
> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_ABLE_SHIFT, 4, 0),
> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_PMICNTR_SHIFT, 4, 0),
> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_SPMU_SHIFT, 4, 0),
> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_CTX_CMPs_SHIFT, 8, 0),

I find it very weird for this to be non-strict when DFR0.CTX_CMPs _is_
strict.

> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_WRPs_SHIFT, 8, 0),
> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_BRPs_SHIFT, 8, 0),

Given that things like hw_breakpoint_reset() rely on the sanitised
register view to determine the number of {break,watch}points, I think we
have to be strict here unless that is changed.

> +	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64DFR1_EL1_SYSPMUID_SHIFT, 8, 0),

Again, I'm not sure that FTR_LOWER_SAFE makes a lot of sense here, but
it's hard to tell without an upstream driver for the system PMU.

> +	ARM64_FTR_END,
> +};
> +
>  static const struct arm64_ftr_bits ftr_mvfr0[] = {
>  	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, MVFR0_EL1_FPRound_SHIFT, 4, 0),
>  	ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, MVFR0_EL1_FPShVec_SHIFT, 4, 0),
> @@ -756,10 +771,6 @@ static const struct arm64_ftr_bits ftr_single32[] = {
>  	ARM64_FTR_END,
>  };
>  
> -static const struct arm64_ftr_bits ftr_raz[] = {
> -	ARM64_FTR_END,
> -};
> -
>  #define __ARM64_FTR_REG_OVERRIDE(id_str, id, table, ovr) {	\
>  		.sys_id = id,					\
>  		.reg = 	&(struct arm64_ftr_reg){		\
> @@ -832,7 +843,7 @@ static const struct __ftr_reg_entry {
>  
>  	/* Op1 = 0, CRn = 0, CRm = 5 */
>  	ARM64_FTR_REG(SYS_ID_AA64DFR0_EL1, ftr_id_aa64dfr0),
> -	ARM64_FTR_REG(SYS_ID_AA64DFR1_EL1, ftr_raz),
> +	ARM64_FTR_REG(SYS_ID_AA64DFR1_EL1, ftr_id_aa64dfr1),

I'm guessing that KVM will need some updates for this in its sys reg
handling code?

Will

^ permalink raw reply

* Re: [PATCH v4 3/6] arm64: hw_breakpoint: Add lockdep_assert_irqs_disabled() on install/uninstall
From: Will Deacon @ 2026-05-28 10:57 UTC (permalink / raw)
  To: Rob Herring (Arm)
  Cc: Mark Rutland, Catalin Marinas, Jonathan Corbet, Shuah Khan,
	Anshuman Khandual, linux-arm-kernel, linux-perf-users,
	linux-kernel, linux-doc
In-Reply-To: <20260407-arm-debug-8-9-v4-3-a4864e69b0ea@kernel.org>

On Tue, Apr 07, 2026 at 09:29:45AM -0500, Rob Herring (Arm) wrote:
> The breakpoint install/uninstall/restore code depends on interrupts
> being disabled. Make this requirement explicit with a
> lockdep_assert_irqs_disabled() assertion.
> 
> Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
> ---
>  arch/arm64/kernel/hw_breakpoint.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/arch/arm64/kernel/hw_breakpoint.c b/arch/arm64/kernel/hw_breakpoint.c
> index bb39bc759810..a9266dc710b4 100644
> --- a/arch/arm64/kernel/hw_breakpoint.c
> +++ b/arch/arm64/kernel/hw_breakpoint.c
> @@ -231,6 +231,8 @@ static int hw_breakpoint_control(struct perf_event *bp,
>  	enum dbg_active_el dbg_el = debug_exception_level(info->ctrl.privilege);
>  	u32 ctrl;
>  
> +	lockdep_assert_irqs_disabled();

This function (hw_breakpoint_control()) is static and only has three
callers:

  1. Via the cpu hotplug CPUHP_AP_PERF_ARM_HW_BREAKPOINT_STARTING notifier
  2. From arch_install_hw_breakpoint()
  3. From arch_uninstall_hw_breakpoint()

So if we're called with irqs enabled, the core code has gone very wrong
and I don't think we should necessarily be checking that in the arch
backend. We also already have a WARN_ON(preemptible()) in
{enable,disable}_debug_monitors() so if you really want to add this then
please can you spell out why you're specifically concerned about the
preemption-disabled but irq-enabled case in the commit message?

Will

^ permalink raw reply

* Re: [PATCH v4 1/6] arm64: hw_breakpoint: Disallow breakpoints in no kprobe code
From: Will Deacon @ 2026-05-28 10:57 UTC (permalink / raw)
  To: Rob Herring (Arm)
  Cc: Mark Rutland, Catalin Marinas, Jonathan Corbet, Shuah Khan,
	Anshuman Khandual, linux-arm-kernel, linux-perf-users,
	linux-kernel, linux-doc
In-Reply-To: <20260407-arm-debug-8-9-v4-1-a4864e69b0ea@kernel.org>

On Tue, Apr 07, 2026 at 09:29:43AM -0500, Rob Herring (Arm) wrote:
> Taking debug exceptions while manipulating the breakpoints is likely to
> be unsafe. The setting kprobes in the breakpoint code is already
> forbidden, but the setting of h/w breakpoints is not. Copy what x86 does
> and exclude breakpoints that fall within the kprobe section.

It would be good to spell this out a little more clearly, as "likely to
be unsafe" is very vague. There's also plenty of breakpoint handling
code outside of the arch backend (e.g. in kernel/events/) which doesn't
seem to be in the no-kprobes section, so it's not clear why that's ok.

Will

^ permalink raw reply

* 答复: 答复: [外部邮件] Re: [PATCH] mm/mempool: use static key for boot-time debug enablement
From: Li,Rongqing(ACG CCN) @ 2026-05-28 10:50 UTC (permalink / raw)
  To: Usama Arif
  Cc: Jonathan Corbet, Shuah Khan, Vlastimil Babka, Harry Yoo,
	Andrew Morton, Hao Li, Christoph Lameter, David Rientjes,
	Roman Gushchin, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-mm@kvack.org
In-Reply-To: <ddb499d5-6821-4fa7-9fec-563bdfbc8cbc@linux.dev>



> 
> On 28/05/2026 04:00, Li,Rongqing(ACG CCN) wrote:
> >>> From: Li RongQing <lirongqing@baidu.com>
> >>>
> >>> Replace the #ifdef CONFIG_SLUB_DEBUG_ON conditional compilation with
> >>> a static key (mempool_debug_enabled). This allows enabling mempool
> >>> debugging at boot time via:
> >>>
> >>>     mempool_debug
> >>>
> >>> Instead of requiring CONFIG_SLUB_DEBUG_ON at compile time. Benefits:
> >>>
> >>> - Debugging can be enabled without rebuilding the kernel
> >>> - Uses standard kernel static_key mechanism with minimal overhead
> >>>
> >>> Suggested-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
> >>> Signed-off-by: Li RongQing <lirongqing@baidu.com>
> >>> Cc: Vlastimil Babka <vbabka@kernel.org>
> >>> Cc: Harry Yoo <harry@kernel.org>
> >>> Cc: Andrew Morton <akpm@linux-foundation.org>
> >>> Cc: Hao Li <hao.li@linux.dev>
> >>> Cc: Christoph Lameter <cl@gentwo.org>
> >>> Cc: David Rientjes <rientjes@google.com>
> >>> Cc: Roman Gushchin <roman.gushchin@linux.dev>
> >>> ---
> >>>  Documentation/admin-guide/kernel-parameters.txt |  5 ++++
> >>>  mm/mempool.c                                    | 32
> >> ++++++++++++++++++-------
> >>>  2 files changed, 28 insertions(+), 9 deletions(-)
> >>>
> >>> diff --git a/Documentation/admin-guide/kernel-parameters.txt
> >>> b/Documentation/admin-guide/kernel-parameters.txt
> >>> index 35ed9dc..5a070e6 100644
> >>> --- a/Documentation/admin-guide/kernel-parameters.txt
> >>> +++ b/Documentation/admin-guide/kernel-parameters.txt
> >>> @@ -3998,6 +3998,11 @@ Kernel parameters
> >>>  			Note that even when enabled, there are a few cases where
> >>>  			the feature is not effective.
> >>>
> >>> +	mempool_debug	[MM]
> >>> +			Enable mempool debugging. This enables element
> >>> +			poison checking when freeing elements back to the
> >>> +			pool. Useful for debugging mempool corruption.
> >>> +
> >>>  	memtest=	[KNL,X86,ARM,M68K,PPC,RISCV,EARLY] Enable
> memtest
> >>>  			Format: <integer>
> >>>  			default : 0 <disable>
> >>> diff --git a/mm/mempool.c b/mm/mempool.c index db23e0e..4f429a1
> >> 100644
> >>> --- a/mm/mempool.c
> >>> +++ b/mm/mempool.c
> >>> @@ -16,11 +16,28 @@
> >>>  #include <linux/export.h>
> >>>  #include <linux/mempool.h>
> >>>  #include <linux/writeback.h>
> >>> +#include <linux/static_key.h>
> >>> +#include <linux/init.h>
> >>>  #include "slab.h"
> >>>
> >>>  static DECLARE_FAULT_ATTR(fail_mempool_alloc);
> >>>  static DECLARE_FAULT_ATTR(fail_mempool_alloc_bulk);
> >>>
> >>> +/*
> >>> + * Debugging support for mempool using static key.
> >>> + *
> >>> + * This allows enabling mempool debug at boot time via:
> >>> + *   mempool_debug
> >>> + */
> >>> +static DEFINE_STATIC_KEY_FALSE(mempool_debug_enabled);
> >>> +
> >>> +static int __init mempool_debug_setup(char *str) {
> >>> +	static_branch_enable(&mempool_debug_enabled);
> >>> +	return 0;
> >>> +}
> >>> +early_param("mempool_debug", mempool_debug_setup);
> >>> +
> >>
> >> Can static_branch_enable() in mempool_debug_setup() run before
> >> jump_label_init() has set static_key_initialized?
> >>
> >> Looking at start_kernel() in init/main.c:
> >>
> >> 	setup_arch(&command_line);
> >> 	mm_core_init_early();
> >> 	/* Static keys and static calls are needed by LSMs */
> >> 	jump_label_init();
> >> 	...
> >> 	/* parameters may set static keys */
> >> 	parse_early_param();
> >>
> >> This will trigger the warning in include/linux/jump_label.h has:
> >>
> >> 	#define STATIC_KEY_CHECK_USE(key) WARN(!static_key_initialized, \
> >> 	    "%s(): static key '%pS' used before call to jump_label_init()", \
> >> 	    __func__, (key))
> >>
> >>
> >> mm/dmapool.c registers an equivalent debug toggle via __setup()
> >> rather than
> >> early_param():
> >>
> >> 	static int __init dmapool_debug_setup(char *str)
> >> 	{
> >> 		static_branch_enable(&dmapool_debug_enabled);
> >> 		return 1;
> >> 	}
> >> 	__setup("dmapool_debug", dmapool_debug_setup);
> >>
> >> I think you can reuse that.
> >
> > Thanks for your review!
> >
> > While this boot-time ordering used to be a generic issue, it seems
> > many architectures have already aligned or fixed this internally. For
> > instance,
> >
> > commit ca829e05d3d4 ("powerpc/64: Init jump labels before
> > parse_early_param()") and commit 6070970db9fe ("m68k: Initialize jump
> > labels early during setup_arch()") explicitly relocated jump_label_init() before
> the early parameter parsing.
> >
> 
> I think 32 bit ARM doesnt?

You are right, 32-bit ARM doesn't. 

However, the correct architectural approach should be fixing the boot sequence 
inside arch/arm/ to match arm64 , powerpc and m68k, rather than compromising core MM 
code with temporary boilerplate variables.

I prefer to keep the mempool implementation clean. If ARM32 triggers the 
warning, the proper remedy is a follow-up patch to align its setup_arch() 
ordering.

What do you think?

-LiRongQing

> 
> > Furthermore, leveraging early_param() to directly manage static keys
> > is still actively used and accepted in the current core kernel. Some examples
> include:
> >
> >   - early_param("randomize_kstack_offset", early_randomize_kstack_offset);
> >   - early_param("threadirqs", setup_forced_irqthreads);
> >
> > The primary reason for using early_param() here instead of __setup()
> > is that mempool allocations can happen extremely early during the boot
> > phase. Moving this to a later stage like __setup() would mean missing
> > the tracking for the most critical early-stage memory pools, which
> > defeats the purpose of boot-time debugging.
> 
> Ack
> 
> >


^ permalink raw reply

* Re: [PATCH v3 1/5] KVM: PPC: Book3S HV: Validate arch_compat against host compatibility mode
From: Ritesh Harjani @ 2026-05-28  3:13 UTC (permalink / raw)
  To: Amit Machhiwal, linuxppc-dev, Madhavan Srinivasan
  Cc: Vaibhav Jain, Amit Machhiwal, Anushree Mathur, Paolo Bonzini,
	Nicholas Piggin, Michael Ellerman, Christophe Leroy (CS GROUP),
	Jonathan Corbet, Shuah Khan, kvm, linux-kernel, linux-doc, lkp
In-Reply-To: <20260522152744.55251-2-amachhiw@linux.ibm.com>

Amit Machhiwal <amachhiw@linux.ibm.com> writes:

> On IBM POWER systems, newer processor generations can operate in
> compatibility modes corresponding to earlier generations. This becomes
> relevant for nested virtualization, where nested KVM guests may need to
> run with a specific processor compatibility level.
>
> Currently, when running a nested KVM guest (L2) inside a Power11 pSeries
> logical partition (L1) booted in Power10 compatibility mode, the guest
> fails to boot while setting 'arch_compat'. This happens because the CPU
> class is derived from the hardware PVR (via mfspr()), which reflects the
> physical processor generation (Power11), rather than the effective
> compatibility mode (Power10).
>
> As a result, userspace may request a Power11 arch_compat for the L2
> guest. However, the L1 partition, running in Power10 compatibility, has
> only negotiated support up to Power10 with the Power Hypervisor (L0).
> When H_SET_STATE is invoked with a Power11 Logical PVR, the hypervisor

s/H_SET_STATE/H_GUEST_SET_STATE 

> rejects the request, leading to a late guest boot failure:
>
>   KVM-NESTEDv2: couldn't set guest wide elements
>   [..KVM reg dump..]
>

I think irrespective of the other UAPI changes, we should still get this
fixed - so that we don't see a late KVM guest boot failure msgs.

So, in this review, I would like to mainly look at fixing this issue
first and would request if we can defer the UAPI changes as a separate
patch series please.


> This situation should be detected earlier. Rejecting unsupported
> 'arch_compat' values in 'kvmppc_set_arch_compat()' avoids issuing an
> invalid H_SET_STATE hcall and provides a clearer failure mode.

s/H_SET_STATE/H_GUEST_SET_STATE

>
> Add a check to reject Power11 'arch_compat' requests when the host is
> running in Power10 compatibility mode, returning -EINVAL early instead
> of deferring the failure to the hypervisor.
>
> Suggested-by: Vaibhav Jain <vaibhav@linux.ibm.com>
> Tested-by: Anushree Mathur <anushree.mathur@linux.ibm.com>
> Signed-off-by: Amit Machhiwal <amachhiw@linux.ibm.com>
> ---
>  arch/powerpc/kvm/book3s_hv.c | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
>
> diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c
> index 61dbeea317f3..249d1f2e4e2c 100644
> --- a/arch/powerpc/kvm/book3s_hv.c
> +++ b/arch/powerpc/kvm/book3s_hv.c
> @@ -446,7 +446,19 @@ static int kvmppc_set_arch_compat(struct kvm_vcpu *vcpu, u32 arch_compat)
>  			guest_pcr_bit = PCR_ARCH_300;
>  			break;
>  		case PVR_ARCH_31:
> +			guest_pcr_bit = PCR_ARCH_31;
> +			break;
>  		case PVR_ARCH_31_P11:
> +			/*
> +			 * Need to check this for ISA 3.1, as Power10 and
> +			 * Power11 share the same PCR. For any subsequent ISA
> +			 * versions, this will be taken care of by the guest vs
> +			 * host PCR comparison below.
> +			 */
> +			if ((PVR_ARCH_31 & cur_cpu_spec->pvr_mask) ==
> +				cur_cpu_spec->pvr_value) {
> +				return -EINVAL;
> +			}

Instead of the complicated check can we simply do this?
			if (!cpu_has_feature(CPU_FTR_P11_PVR))
				return -EINVAL;

which means that if the Qemu is trying to set the arch_compat with P11
PVR (arch_compat) and if the host cpu FTR doesn't support P11 PVR, then
simply return -EINVAL

-ritesh


^ permalink raw reply

* Re: [PATCH v3] Documentation/process: Add Researcher Guidelines
From: Dan Carpenter @ 2026-05-28 10:34 UTC (permalink / raw)
  To: Kees Cook
  Cc: Jonathan Corbet, Greg Kroah-Hartman, Stefano Zacchiroli,
	Steven Rostedt, Laura Abbott, Julia Lawall, Wenwen Wang,
	Gustavo A . R . Silva, Thorsten Leemhuis, linux-kernel, linux-doc,
	linux-hardening, Dawei Feng
In-Reply-To: <20220304181418.1692016-1-keescook@chromium.org>

On Fri, Mar 04, 2022 at 10:14:18AM -0800, Kees Cook wrote:
> +For example::
> +
> +  From: Author <author@email>
> +  Subject: [PATCH] drivers/foo_bar: Add missing kfree()
> +
> +  The error path in foo_bar driver does not correctly free the allocated
> +  struct foo_bar_info. This can happen if the attached foo_bar device
> +  rejects the initialization packets sent during foo_bar_probe(). This
> +  would result in a 64 byte slab memory leak once per device attach,
> +  wasting memory resources over time.
> +
> +  This flaw was found using an experimental static analysis tool we are
> +  developing, LeakMagic[1], which reported the following warning when
> +  analyzing the v5.15 kernel release:
> +
> +   path/to/foo_bar.c:187: missing kfree() call?
> +
> +  Add the missing kfree() to the error path. No other references to
> +  this memory exist outside the probe function, so this is the only
> +  place it can be freed.
> +
> +  x86_64 and arm64 defconfig builds with CONFIG_FOO_BAR=y using GCC
> +  11.2 show no new warnings, and LeakMagic no longer warns about this
> +  code path. As we don't have a FooBar device to test with, no runtime
> +  testing was able to be performed.

People have started sending commit messages in this exact template and
normally I would ask them resend with the meta commentary from this
paragraph below the --- cut off line.

Do we really want this "Compile tested only" stuff in the permanent git
log?

regards,
dan carpenter

> +
> +  [1] https://url/to/leakmagic/details
> +
> +  Reported-by: Researcher <researcher@email>
> +  Fixes: aaaabbbbccccdddd ("Introduce support for FooBar")
> +  Signed-off-by: Author <author@email>
> +  Reviewed-by: Reviewer <reviewer@email>
> +


^ permalink raw reply

* Re: 答复: [外部邮件] Re: [PATCH] mm/mempool: use static key for boot-time debug enablement
From: Usama Arif @ 2026-05-28 10:33 UTC (permalink / raw)
  To: Li,Rongqing(ACG CCN)
  Cc: Jonathan Corbet, Shuah Khan, Vlastimil Babka, Harry Yoo,
	Andrew Morton, Hao Li, Christoph Lameter, David Rientjes,
	Roman Gushchin, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-mm@kvack.org
In-Reply-To: <fcf5585aba18414cbd0ab01935eeb1df@baidu.com>



On 28/05/2026 04:00, Li,Rongqing(ACG CCN) wrote:
>>> From: Li RongQing <lirongqing@baidu.com>
>>>
>>> Replace the #ifdef CONFIG_SLUB_DEBUG_ON conditional compilation with a
>>> static key (mempool_debug_enabled). This allows enabling mempool
>>> debugging at boot time via:
>>>
>>>     mempool_debug
>>>
>>> Instead of requiring CONFIG_SLUB_DEBUG_ON at compile time. Benefits:
>>>
>>> - Debugging can be enabled without rebuilding the kernel
>>> - Uses standard kernel static_key mechanism with minimal overhead
>>>
>>> Suggested-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
>>> Signed-off-by: Li RongQing <lirongqing@baidu.com>
>>> Cc: Vlastimil Babka <vbabka@kernel.org>
>>> Cc: Harry Yoo <harry@kernel.org>
>>> Cc: Andrew Morton <akpm@linux-foundation.org>
>>> Cc: Hao Li <hao.li@linux.dev>
>>> Cc: Christoph Lameter <cl@gentwo.org>
>>> Cc: David Rientjes <rientjes@google.com>
>>> Cc: Roman Gushchin <roman.gushchin@linux.dev>
>>> ---
>>>  Documentation/admin-guide/kernel-parameters.txt |  5 ++++
>>>  mm/mempool.c                                    | 32
>> ++++++++++++++++++-------
>>>  2 files changed, 28 insertions(+), 9 deletions(-)
>>>
>>> diff --git a/Documentation/admin-guide/kernel-parameters.txt
>>> b/Documentation/admin-guide/kernel-parameters.txt
>>> index 35ed9dc..5a070e6 100644
>>> --- a/Documentation/admin-guide/kernel-parameters.txt
>>> +++ b/Documentation/admin-guide/kernel-parameters.txt
>>> @@ -3998,6 +3998,11 @@ Kernel parameters
>>>  			Note that even when enabled, there are a few cases where
>>>  			the feature is not effective.
>>>
>>> +	mempool_debug	[MM]
>>> +			Enable mempool debugging. This enables element
>>> +			poison checking when freeing elements back to the
>>> +			pool. Useful for debugging mempool corruption.
>>> +
>>>  	memtest=	[KNL,X86,ARM,M68K,PPC,RISCV,EARLY] Enable memtest
>>>  			Format: <integer>
>>>  			default : 0 <disable>
>>> diff --git a/mm/mempool.c b/mm/mempool.c index db23e0e..4f429a1
>> 100644
>>> --- a/mm/mempool.c
>>> +++ b/mm/mempool.c
>>> @@ -16,11 +16,28 @@
>>>  #include <linux/export.h>
>>>  #include <linux/mempool.h>
>>>  #include <linux/writeback.h>
>>> +#include <linux/static_key.h>
>>> +#include <linux/init.h>
>>>  #include "slab.h"
>>>
>>>  static DECLARE_FAULT_ATTR(fail_mempool_alloc);
>>>  static DECLARE_FAULT_ATTR(fail_mempool_alloc_bulk);
>>>
>>> +/*
>>> + * Debugging support for mempool using static key.
>>> + *
>>> + * This allows enabling mempool debug at boot time via:
>>> + *   mempool_debug
>>> + */
>>> +static DEFINE_STATIC_KEY_FALSE(mempool_debug_enabled);
>>> +
>>> +static int __init mempool_debug_setup(char *str) {
>>> +	static_branch_enable(&mempool_debug_enabled);
>>> +	return 0;
>>> +}
>>> +early_param("mempool_debug", mempool_debug_setup);
>>> +
>>
>> Can static_branch_enable() in mempool_debug_setup() run before
>> jump_label_init() has set static_key_initialized?
>>
>> Looking at start_kernel() in init/main.c:
>>
>> 	setup_arch(&command_line);
>> 	mm_core_init_early();
>> 	/* Static keys and static calls are needed by LSMs */
>> 	jump_label_init();
>> 	...
>> 	/* parameters may set static keys */
>> 	parse_early_param();
>>
>> This will trigger the warning in include/linux/jump_label.h has:
>>
>> 	#define STATIC_KEY_CHECK_USE(key) WARN(!static_key_initialized, \
>> 	    "%s(): static key '%pS' used before call to jump_label_init()", \
>> 	    __func__, (key))
>>
>>
>> mm/dmapool.c registers an equivalent debug toggle via __setup() rather than
>> early_param():
>>
>> 	static int __init dmapool_debug_setup(char *str)
>> 	{
>> 		static_branch_enable(&dmapool_debug_enabled);
>> 		return 1;
>> 	}
>> 	__setup("dmapool_debug", dmapool_debug_setup);
>>
>> I think you can reuse that.
> 
> Thanks for your review!
> 
> While this boot-time ordering used to be a generic issue, it seems many
> architectures have already aligned or fixed this internally. For instance,
> 
> commit ca829e05d3d4 ("powerpc/64: Init jump labels before parse_early_param()")
> and commit 6070970db9fe ("m68k: Initialize jump labels early during setup_arch()")
> explicitly relocated jump_label_init() before the early parameter parsing.
> 

I think 32 bit ARM doesnt? 

> Furthermore, leveraging early_param() to directly manage static keys is still
> actively used and accepted in the current core kernel. Some examples include:
> 
>   - early_param("randomize_kstack_offset", early_randomize_kstack_offset);
>   - early_param("threadirqs", setup_forced_irqthreads);
> 
> The primary reason for using early_param() here instead of __setup() is that
> mempool allocations can happen extremely early during the boot phase. Moving
> this to a later stage like __setup() would mean missing the tracking for the
> most critical early-stage memory pools, which defeats the purpose of boot-time
> debugging.

Ack

> 
> Therefore, I think using early_param() here is the most robust option to
> ensure full coverage of mempool allocations.
> 
> What do you think?
> > Thanks
> 
> -Li
> 
> 
>>
>>>  static int __init mempool_faul_inject_init(void)  {
>>>  	int error;
>>> @@ -37,7 +54,6 @@ static int __init mempool_faul_inject_init(void)  }
>>> late_initcall(mempool_faul_inject_init);
>>>
>>> -#ifdef CONFIG_SLUB_DEBUG_ON
>>>  static void poison_error(struct mempool *pool, void *element, size_t size,
>>>  			 size_t byte)
>>>  {
>>> @@ -73,6 +89,9 @@ static void __check_element(struct mempool *pool,
>>> void *element, size_t size)
>>>
>>>  static void check_element(struct mempool *pool, void *element)  {
>>> +	if (!static_branch_unlikely(&mempool_debug_enabled))
>>> +		return;
>>> +
>>>  	/* Skip checking: KASAN might save its metadata in the element. */
>>>  	if (kasan_enabled())
>>>  		return;
>>> @@ -112,6 +131,9 @@ static void __poison_element(void *element, size_t
>>> size)
>>>
>>>  static void poison_element(struct mempool *pool, void *element)  {
>>> +	if (!static_branch_unlikely(&mempool_debug_enabled))
>>> +		return;
>>> +
>>
>> Before this change, building with CONFIG_SLUB_DEBUG_ON=y compiled in
>> check_element() and poison_element() unconditionally, so the poisoning and
>> corruption checks ran on every mempool free/alloc.
>> After this change those checks are gated on the mempool_debug boot parameter
>> even when CONFIG_SLUB_DEBUG_ON=y.
>>
>> Existing users who relied on CONFIG_SLUB_DEBUG_ON=y giving them mempool
>> poison checking will silently lose it on upgrade unless they also add
>> "mempool_debug" to the command line.
>>
>> Would it be worth defaulting the static key to true under
>> CONFIG_SLUB_DEBUG_ON=y, for example:
>>
>> 	#ifdef CONFIG_SLUB_DEBUG_ON
>> 	static DEFINE_STATIC_KEY_TRUE(mempool_debug_enabled);
>> 	#else
>> 	static DEFINE_STATIC_KEY_FALSE(mempool_debug_enabled);
>> 	#endif
>>
>> so the previous default behaviour is preserved.
>>
>>
>>>  	/* Skip poisoning: KASAN might save its metadata in the element. */
>>>  	if (kasan_enabled())
>>>  		return;
>>> @@ -140,14 +162,6 @@ static void poison_element(struct mempool *pool,
>>> void *element)  #endif
>>>  	}
>>>  }
>>> -#else /* CONFIG_SLUB_DEBUG_ON */
>>> -static inline void check_element(struct mempool *pool, void *element)
>>> -{ -} -static inline void poison_element(struct mempool *pool, void
>>> *element) -{ -} -#endif /* CONFIG_SLUB_DEBUG_ON */
>>>
>>>  static __always_inline bool kasan_poison_element(struct mempool *pool,
>>>  		void *element)
>>> --
>>> 2.9.4
>>>
>>>


^ permalink raw reply

* [PATCH] PM: sleep: Allow disabling DPM watchdog by default
From: Tzung-Bi Shih @ 2026-05-28 10:32 UTC (permalink / raw)
  To: Jonathan Corbet, Rafael J. Wysocki, Greg Kroah-Hartman,
	Danilo Krummrich
  Cc: Shuah Khan, Pavel Machek, Len Brown, tzungbi, linux-doc,
	linux-kernel, linux-pm, driver-core

Introduce the CONFIG_DPM_WATCHDOG_DEFAULT_ENABLED Kconfig option to
allow the device suspend/resume watchdog (DPM watchdog) to be disabled
by default at compile time.

Additionally, introduce the "dpm_watchdog_enabled" boot parameter to
enable or disable the watchdog at boot time.

This provides flexibility for systems that want the watchdog code
compiled in but inactive by default, allowing it to be enabled only when
needed.

Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
 .../admin-guide/kernel-parameters.txt         |  8 ++++++++
 drivers/base/power/main.c                     | 20 +++++++++++++++++++
 kernel/power/Kconfig                          |  9 +++++++++
 3 files changed, 37 insertions(+)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 85936e48cf9a..3a919e660137 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1344,6 +1344,14 @@ Kernel parameters
 			it becomes active and is searched during signature
 			verification.
 
+	dpm_watchdog_enabled=
+			[KNL] Enable or disable the device suspend/resume
+			watchdog (DPM watchdog).
+			Format: {"0" | "1"}
+			0: disable
+			1: enable
+			Default value is set by CONFIG_DPM_WATCHDOG_DEFAULT_ENABLED.
+
 	driver_async_probe=  [KNL]
 			List of driver names to be probed asynchronously. *
 			matches with all driver names. If * is specified, the
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index e1b550664bab..4f92905f3edf 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -527,6 +527,20 @@ module_param(dpm_watchdog_all_cpu_backtrace, bool, 0644);
 MODULE_PARM_DESC(dpm_watchdog_all_cpu_backtrace,
 		 "Backtrace all CPUs on DPM watchdog timeout");
 
+#ifdef CONFIG_DPM_WATCHDOG_DEFAULT_ENABLED
+static unsigned int __read_mostly dpm_watchdog_enabled = 1;
+#else
+static unsigned int __read_mostly dpm_watchdog_enabled;
+#endif
+
+static int __init dpm_watchdog_setup(char *str)
+{
+	if (kstrtouint(str, 0, &dpm_watchdog_enabled) == 0)
+		return 1;
+	return 0;
+}
+__setup("dpm_watchdog_enabled=", dpm_watchdog_setup);
+
 /**
  * dpm_watchdog_handler - Driver suspend / resume watchdog handler.
  * @t: The timer that PM watchdog depends on.
@@ -570,6 +584,9 @@ static void dpm_watchdog_set(struct dpm_watchdog *wd, struct device *dev)
 {
 	struct timer_list *timer = &wd->timer;
 
+	if (!dpm_watchdog_enabled)
+		return;
+
 	wd->dev = dev;
 	wd->tsk = current;
 	wd->fatal = CONFIG_DPM_WATCHDOG_TIMEOUT == CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT;
@@ -588,6 +605,9 @@ static void dpm_watchdog_clear(struct dpm_watchdog *wd)
 {
 	struct timer_list *timer = &wd->timer;
 
+	if (!dpm_watchdog_enabled)
+		return;
+
 	timer_delete_sync(timer);
 	timer_destroy_on_stack(timer);
 }
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 05337f437cca..d4cecdb8575e 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -267,6 +267,15 @@ config DPM_WATCHDOG
 	  captured in pstore device for inspection in subsequent
 	  boot session.
 
+config DPM_WATCHDOG_DEFAULT_ENABLED
+	bool "Enable DPM watchdog by default"
+	depends on DPM_WATCHDOG
+	default y
+	help
+	  If you say Y here, the DPM watchdog will be enabled by default.
+	  If you say N, it will be compiled in but disabled, requiring a
+	  boot parameter to activate.
+
 config DPM_WATCHDOG_TIMEOUT
 	int "Watchdog timeout to panic in seconds"
 	range 1 120
-- 
2.54.0.929.g9b7fa37559-goog


^ permalink raw reply related

* [RFC PATCH v1 13/13] selftests/exec: cover spawn template basics
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Add exec selftests for the spawn_template ABI. Cover basic spawning,
relative path rejection, execfd execute-permission checks, default fd
closing, close-range actions using newfd -1, and stale path rejection
after executable metadata changes.

Also cover atomic path replacement while a template fd for an old path is
still alive. The old template must reject the changed path with ESTALE, and
a new template for the same path must execute the replacement.

Signed-off-by: Li Chen <me@linux.beauty>
---
 MAINTAINERS                                   |   1 +
 tools/testing/selftests/exec/Makefile         |   1 +
 tools/testing/selftests/exec/spawn_template.c | 997 ++++++++++++++++++
 3 files changed, 999 insertions(+)
 create mode 100644 tools/testing/selftests/exec/spawn_template.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 3e737097940f9..77b3da32b4d2a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9747,6 +9747,7 @@ F:	include/uapi/linux/spawn_template.h
 F:	kernel/fork.c
 F:	mm/vma_exec.c
 F:	tools/testing/selftests/exec/
+F:	tools/testing/selftests/exec/spawn_template.c
 N:	asm/elf.h
 N:	binfmt
 
diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile
index 45a3cfc435cfd..cf39fe916b9ba 100644
--- a/tools/testing/selftests/exec/Makefile
+++ b/tools/testing/selftests/exec/Makefile
@@ -20,6 +20,7 @@ TEST_FILES := Makefile
 TEST_GEN_PROGS += recursion-depth
 TEST_GEN_PROGS += null-argv
 TEST_GEN_PROGS += check-exec
+TEST_GEN_PROGS += spawn_template
 
 EXTRA_CLEAN := $(OUTPUT)/subdir.moved $(OUTPUT)/execveat.moved $(OUTPUT)/xxxxx*	\
 	       $(OUTPUT)/S_I*.test
diff --git a/tools/testing/selftests/exec/spawn_template.c b/tools/testing/selftests/exec/spawn_template.c
new file mode 100644
index 0000000000000..26708143ac9dc
--- /dev/null
+++ b/tools/testing/selftests/exec/spawn_template.c
@@ -0,0 +1,997 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/spawn_template.h>
+
+#include "kselftest.h"
+
+#ifndef __NR_spawn_template_create
+#define __NR_spawn_template_create 472
+#endif
+
+#ifndef __NR_spawn_template_spawn
+#define __NR_spawn_template_spawn 473
+#endif
+
+#define SPAWN_TEMPLATE_MISSING_SYSCALL_ERRNO	38
+#define SPAWN_TEMPLATE_KERNEL_NSIG		64
+#define SPAWN_TEMPLATE_KERNEL_SIGSET_WORDS	\
+	(SPAWN_TEMPLATE_KERNEL_NSIG / (8 * sizeof(unsigned long)))
+
+static const char *true_path;
+static char self_path[PATH_MAX];
+
+struct spawn_template_kernel_sigset {
+	unsigned long sig[SPAWN_TEMPLATE_KERNEL_SIGSET_WORDS];
+};
+
+static void spawn_template_kernel_sigempty(struct spawn_template_kernel_sigset *set)
+{
+	memset(set, 0, sizeof(*set));
+}
+
+static void spawn_template_kernel_sigadd(struct spawn_template_kernel_sigset *set,
+					 int sig)
+{
+	sig--;
+	set->sig[sig / (8 * sizeof(unsigned long))] |=
+		1UL << (sig % (8 * sizeof(unsigned long)));
+}
+
+static int read_fd_string(int fd, const char *expected)
+{
+	char buf[128];
+	ssize_t nread;
+
+	nread = read(fd, buf, sizeof(buf) - 1);
+	if (nread < 0)
+		return -errno;
+
+	buf[nread] = '\0';
+	return strcmp(buf, expected) ? -EINVAL : 0;
+}
+
+static int write_file(const char *path, const char *data, mode_t mode)
+{
+	size_t left = strlen(data);
+	const char *p = data;
+	int fd;
+	int ret = 0;
+
+	fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
+	if (fd < 0)
+		return -errno;
+
+	while (left) {
+		ssize_t written = write(fd, p, left);
+
+		if (written < 0) {
+			ret = -errno;
+			break;
+		}
+		left -= written;
+		p += written;
+	}
+
+	close(fd);
+	return ret;
+}
+
+static int create_template_path(const char *path)
+{
+	struct spawn_template_create_args args = {
+		.flags = SPAWN_TEMPLATE_CREATE_CLOEXEC,
+		.execfd = -1,
+		.filename = (uintptr_t)path,
+	};
+
+	return syscall(__NR_spawn_template_create, &args, sizeof(args));
+}
+
+static int create_template_fd(int execfd)
+{
+	struct spawn_template_create_args args = {
+		.flags = SPAWN_TEMPLATE_CREATE_CLOEXEC,
+		.execfd = execfd,
+	};
+
+	return syscall(__NR_spawn_template_create, &args, sizeof(args));
+}
+
+static int spawn_template_start(int template_fd, char *const argv[],
+				struct spawn_template_action *actions,
+				unsigned int actions_len,
+				unsigned long long flags, pid_t *pid_out,
+				int *pidfd_out)
+{
+	char *const envp[] = { "PATH=/usr/bin:/bin", NULL };
+	struct spawn_template_spawn_args args = {
+		.flags = flags,
+		.argv = (uintptr_t)argv,
+		.envp = (uintptr_t)envp,
+		.actions = (uintptr_t)actions,
+		.actions_len = actions_len,
+	};
+	int pidfd = -1;
+	pid_t pid;
+	int ret;
+
+	args.pidfd = (uintptr_t)&pidfd;
+
+	pid = syscall(__NR_spawn_template_spawn, template_fd, &args,
+		      sizeof(args));
+	if (pid < 0) {
+		ret = -errno;
+		if (pidfd >= 0) {
+			siginfo_t info;
+
+			waitid(P_PIDFD, pidfd, &info, WEXITED);
+			close(pidfd);
+		}
+		return ret;
+	}
+
+	*pid_out = pid;
+	*pidfd_out = pidfd;
+	return 0;
+}
+
+static int spawn_template(int template_fd, char *const argv[],
+			  struct spawn_template_action *actions,
+			  unsigned int actions_len, unsigned long long flags)
+{
+	siginfo_t info = {};
+	int pidfd;
+	pid_t pid;
+	int ret;
+
+	ret = spawn_template_start(template_fd, argv, actions, actions_len, flags,
+				   &pid, &pidfd);
+	if (ret)
+		return ret;
+	(void)pid;
+
+	ret = waitid(P_PIDFD, pidfd, &info, WEXITED);
+	if (ret < 0) {
+		ret = -errno;
+		goto out_close_pidfd;
+	}
+
+	if (info.si_code != CLD_EXITED) {
+		ret = -EINVAL;
+		goto out_close_pidfd;
+	}
+
+	ret = info.si_status;
+
+out_close_pidfd:
+	if (pidfd >= 0)
+		close(pidfd);
+	return ret;
+}
+
+static const char *find_true(void)
+{
+	static const char * const paths[] = {
+		"/usr/bin/true",
+		"/bin/true",
+	};
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(paths); i++) {
+		if (access(paths[i], X_OK) == 0)
+			return paths[i];
+	}
+	return NULL;
+}
+
+static int copy_file(const char *src, const char *dst)
+{
+	char buf[8192];
+	ssize_t nread;
+	int infd;
+	int outfd;
+	int ret = 0;
+
+	infd = open(src, O_RDONLY | O_CLOEXEC);
+	if (infd < 0)
+		return -errno;
+
+	outfd = open(dst, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0700);
+	if (outfd < 0) {
+		ret = -errno;
+		goto out_close_in;
+	}
+
+	while ((nread = read(infd, buf, sizeof(buf))) > 0) {
+		char *p = buf;
+		ssize_t left = nread;
+
+		while (left > 0) {
+			ssize_t written = write(outfd, p, left);
+
+			if (written < 0) {
+				ret = -errno;
+				goto out_close_out;
+			}
+			left -= written;
+			p += written;
+		}
+	}
+	if (nread < 0)
+		ret = -errno;
+
+out_close_out:
+	close(outfd);
+out_close_in:
+	close(infd);
+	return ret;
+}
+
+static int test_basic_spawn(void)
+{
+	char *const argv[] = { (char *)true_path, NULL };
+	int template_fd;
+	int ret;
+
+	template_fd = create_template_path(true_path);
+	if (template_fd < 0)
+		return -errno;
+
+	ret = spawn_template(template_fd, argv, NULL, 0, 0);
+	close(template_fd);
+	return ret;
+}
+
+static int test_relative_path_rejected(void)
+{
+	int template_fd;
+
+	template_fd = create_template_path("true");
+	if (template_fd >= 0) {
+		close(template_fd);
+		return -EINVAL;
+	}
+
+	return errno == EINVAL ? 0 : -errno;
+}
+
+static int test_execfd_requires_execute(void)
+{
+	char path[] = "/tmp/spawn-template-noexec-XXXXXX";
+	int template_fd;
+	int fd;
+	int ret = 0;
+
+	fd = mkstemp(path);
+	if (fd < 0)
+		return -errno;
+
+	if (fchmod(fd, 0600)) {
+		ret = -errno;
+		goto out;
+	}
+
+	template_fd = create_template_fd(fd);
+	if (template_fd >= 0) {
+		close(template_fd);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = errno == EACCES ? 0 : -errno;
+
+out:
+	close(fd);
+	unlink(path);
+	return ret;
+}
+
+static int test_default_closes_extra_fds(void)
+{
+	char fdarg[32];
+	char *const argv[] = {
+		self_path,
+		"--check-fd-closed",
+		fdarg,
+		NULL,
+	};
+	int template_fd;
+	int extra_fd;
+	int ret;
+
+	extra_fd = open("/dev/null", O_RDONLY);
+	if (extra_fd < 0)
+		return -errno;
+
+	snprintf(fdarg, sizeof(fdarg), "%d", extra_fd);
+
+	template_fd = create_template_path(self_path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_close_extra;
+	}
+
+	ret = spawn_template(template_fd, argv, NULL, 0, 0);
+	close(template_fd);
+
+out_close_extra:
+	close(extra_fd);
+	return ret;
+}
+
+static int test_close_range_max_action(void)
+{
+	char fdarg[32];
+	char *const argv[] = {
+		self_path,
+		"--check-fd-closed",
+		fdarg,
+		NULL,
+	};
+	struct spawn_template_action action = {
+		.type = SPAWN_TEMPLATE_ACTION_CLOSE_RANGE,
+		.fd = -1,
+		.newfd = -1,
+	};
+	int template_fd;
+	int extra_fd;
+	int ret;
+
+	extra_fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
+	if (extra_fd < 0)
+		return -errno;
+
+	action.fd = extra_fd;
+	snprintf(fdarg, sizeof(fdarg), "%d", extra_fd);
+
+	template_fd = create_template_path(self_path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_close_extra;
+	}
+
+	ret = spawn_template(template_fd, argv, &action, 1,
+			     SPAWN_TEMPLATE_SPAWN_INHERIT_FDS);
+	close(template_fd);
+
+out_close_extra:
+	close(extra_fd);
+	return ret;
+}
+
+static int test_dup2_stdio_actions(void)
+{
+	char *const argv[] = { self_path, "--write-stdio", NULL };
+	struct spawn_template_action actions[2];
+	char out_buf[32];
+	char err_buf[32];
+	int out_pipe[2];
+	int err_pipe[2];
+	int template_fd;
+	int ret = 0;
+
+	if (pipe2(out_pipe, O_CLOEXEC))
+		return -errno;
+	if (pipe2(err_pipe, O_CLOEXEC)) {
+		ret = -errno;
+		goto out_close_out_pipe;
+	}
+
+	actions[0] = (struct spawn_template_action) {
+		.type = SPAWN_TEMPLATE_ACTION_DUP2,
+		.fd = out_pipe[1],
+		.newfd = STDOUT_FILENO,
+	};
+	actions[1] = (struct spawn_template_action) {
+		.type = SPAWN_TEMPLATE_ACTION_DUP2,
+		.fd = err_pipe[1],
+		.newfd = STDERR_FILENO,
+	};
+
+	template_fd = create_template_path(self_path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_close_err_pipe;
+	}
+
+	ret = spawn_template(template_fd, argv, actions, ARRAY_SIZE(actions), 0);
+	close(template_fd);
+	if (ret)
+		goto out_close_err_pipe;
+
+	close(out_pipe[1]);
+	out_pipe[1] = -1;
+	close(err_pipe[1]);
+	err_pipe[1] = -1;
+
+	memset(out_buf, 0, sizeof(out_buf));
+	memset(err_buf, 0, sizeof(err_buf));
+	if (read(out_pipe[0], out_buf, sizeof(out_buf) - 1) < 0) {
+		ret = -errno;
+		goto out_close_err_pipe;
+	}
+	if (read(err_pipe[0], err_buf, sizeof(err_buf) - 1) < 0) {
+		ret = -errno;
+		goto out_close_err_pipe;
+	}
+	if (strcmp(out_buf, "stdout-token\n") ||
+	    strcmp(err_buf, "stderr-token\n"))
+		ret = -EINVAL;
+
+out_close_err_pipe:
+	if (err_pipe[1] >= 0)
+		close(err_pipe[1]);
+	close(err_pipe[0]);
+out_close_out_pipe:
+	if (out_pipe[1] >= 0)
+		close(out_pipe[1]);
+	close(out_pipe[0]);
+	return ret;
+}
+
+static int test_open_action_stdin(void)
+{
+	char dir[] = "/tmp/spawn-template-open-XXXXXX";
+	char path[PATH_MAX];
+	char *const argv[] = {
+		self_path,
+		"--check-fd-content",
+		"0",
+		"open-action-token\n",
+		NULL,
+	};
+	struct spawn_template_open open_arg = {
+		.path = (uintptr_t)path,
+		.how = {
+			.flags = O_RDONLY,
+		},
+	};
+	struct spawn_template_action action = {
+		.type = SPAWN_TEMPLATE_ACTION_OPEN,
+		.fd = AT_FDCWD,
+		.newfd = STDIN_FILENO,
+		.arg = (uintptr_t)&open_arg,
+	};
+	int template_fd;
+	int ret;
+
+	if (!mkdtemp(dir))
+		return -errno;
+
+	snprintf(path, sizeof(path), "%s/input", dir);
+	ret = write_file(path, "open-action-token\n", 0600);
+	if (ret)
+		goto out_unlink;
+
+	template_fd = create_template_path(self_path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_unlink;
+	}
+
+	ret = spawn_template(template_fd, argv, &action, 1, 0);
+	close(template_fd);
+
+out_unlink:
+	unlink(path);
+	rmdir(dir);
+	return ret;
+}
+
+static int test_fchdir_action(void)
+{
+	char dir[] = "/tmp/spawn-template-fchdir-XXXXXX";
+	char resolved[PATH_MAX];
+	char *const argv[] = {
+		self_path,
+		"--check-cwd",
+		resolved,
+		NULL,
+	};
+	struct spawn_template_action action = {
+		.type = SPAWN_TEMPLATE_ACTION_FCHDIR,
+	};
+	int template_fd;
+	int dirfd;
+	int ret;
+
+	if (!mkdtemp(dir))
+		return -errno;
+	if (!realpath(dir, resolved)) {
+		ret = -errno;
+		goto out_rmdir;
+	}
+
+	dirfd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+	if (dirfd < 0) {
+		ret = -errno;
+		goto out_rmdir;
+	}
+	action.fd = dirfd;
+
+	template_fd = create_template_path(self_path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_close_dirfd;
+	}
+
+	ret = spawn_template(template_fd, argv, &action, 1, 0);
+	close(template_fd);
+
+out_close_dirfd:
+	close(dirfd);
+out_rmdir:
+	rmdir(dir);
+	return ret;
+}
+
+static int test_sigmask_action(void)
+{
+	char sigarg[16];
+	char *const argv[] = {
+		self_path,
+		"--check-sigmask",
+		sigarg,
+		NULL,
+	};
+	struct spawn_template_kernel_sigset mask;
+	struct spawn_template_sigset sigset_arg = {
+		.sigset = (uintptr_t)&mask,
+		.sigsetsize = sizeof(mask),
+	};
+	struct spawn_template_action action = {
+		.type = SPAWN_TEMPLATE_ACTION_SIGMASK,
+		.arg = (uintptr_t)&sigset_arg,
+	};
+	int template_fd;
+	int ret;
+
+	spawn_template_kernel_sigempty(&mask);
+	spawn_template_kernel_sigadd(&mask, SIGUSR1);
+	snprintf(sigarg, sizeof(sigarg), "%d", SIGUSR1);
+
+	template_fd = create_template_path(self_path);
+	if (template_fd < 0)
+		return -errno;
+
+	ret = spawn_template(template_fd, argv, &action, 1, 0);
+	close(template_fd);
+	return ret;
+}
+
+static int test_sigdefault_action(void)
+{
+	char sigarg[16];
+	char *const argv[] = {
+		self_path,
+		"--check-sigdefault",
+		sigarg,
+		NULL,
+	};
+	struct spawn_template_kernel_sigset mask;
+	struct sigaction old_sa;
+	struct sigaction ignore_sa = {
+		.sa_handler = SIG_IGN,
+	};
+	struct spawn_template_sigset sigset_arg = {
+		.sigset = (uintptr_t)&mask,
+		.sigsetsize = sizeof(mask),
+	};
+	struct spawn_template_action action = {
+		.type = SPAWN_TEMPLATE_ACTION_SIGDEFAULT,
+		.arg = (uintptr_t)&sigset_arg,
+	};
+	int template_fd;
+	int ret;
+
+	spawn_template_kernel_sigempty(&mask);
+	spawn_template_kernel_sigadd(&mask, SIGUSR1);
+	snprintf(sigarg, sizeof(sigarg), "%d", SIGUSR1);
+
+	if (sigaction(SIGUSR1, &ignore_sa, &old_sa))
+		return -errno;
+
+	template_fd = create_template_path(self_path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_restore_signal;
+	}
+
+	ret = spawn_template(template_fd, argv, &action, 1, 0);
+	close(template_fd);
+
+out_restore_signal:
+	sigaction(SIGUSR1, &old_sa, NULL);
+	return ret;
+}
+
+static int test_inherit_fds_flag(void)
+{
+	char fdarg[32];
+	char *const argv[] = {
+		self_path,
+		"--check-fd-open",
+		fdarg,
+		NULL,
+	};
+	int template_fd;
+	int extra_fd;
+	int ret;
+
+	extra_fd = open("/dev/null", O_RDONLY);
+	if (extra_fd < 0)
+		return -errno;
+	snprintf(fdarg, sizeof(fdarg), "%d", extra_fd);
+
+	template_fd = create_template_path(self_path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_close_extra;
+	}
+
+	ret = spawn_template(template_fd, argv, NULL, 0,
+			     SPAWN_TEMPLATE_SPAWN_INHERIT_FDS);
+	close(template_fd);
+
+out_close_extra:
+	close(extra_fd);
+	return ret;
+}
+
+static int test_pidfd_waitid(void)
+{
+	char *const argv[] = { (char *)true_path, NULL };
+	siginfo_t info = {};
+	int template_fd;
+	int pidfd;
+	pid_t pid;
+	int ret;
+
+	template_fd = create_template_path(true_path);
+	if (template_fd < 0)
+		return -errno;
+
+	ret = spawn_template_start(template_fd, argv, NULL, 0, 0, &pid, &pidfd);
+	close(template_fd);
+	if (ret)
+		return ret;
+
+	ret = waitid(P_PIDFD, pidfd, &info, WEXITED);
+	if (ret < 0) {
+		ret = -errno;
+		waitpid(pid, NULL, 0);
+		goto out_close_pidfd;
+	}
+	if (info.si_code != CLD_EXITED || info.si_status)
+		ret = -EINVAL;
+
+out_close_pidfd:
+	close(pidfd);
+	return ret;
+}
+
+static int test_create_actions_rejected(void)
+{
+	struct spawn_template_action action = {
+		.type = SPAWN_TEMPLATE_ACTION_CLOSE,
+		.fd = STDIN_FILENO,
+	};
+	struct spawn_template_create_args args = {
+		.flags = SPAWN_TEMPLATE_CREATE_CLOEXEC,
+		.execfd = -1,
+		.filename = (uintptr_t)true_path,
+		.actions = (uintptr_t)&action,
+		.actions_len = 1,
+	};
+	int template_fd;
+
+	template_fd = syscall(__NR_spawn_template_create, &args, sizeof(args));
+	if (template_fd >= 0) {
+		close(template_fd);
+		return -EINVAL;
+	}
+
+	return errno == EINVAL ? 0 : -errno;
+}
+
+static int test_script_template_unsupported(void)
+{
+	char dir[] = "/tmp/spawn-template-script-XXXXXX";
+	char path[PATH_MAX];
+	int template_fd;
+	int ret;
+
+	if (!mkdtemp(dir))
+		return -errno;
+
+	snprintf(path, sizeof(path), "%s/script", dir);
+	ret = write_file(path, "#!/bin/sh\nexit 0\n", 0700);
+	if (ret)
+		goto out_unlink;
+
+	template_fd = create_template_path(path);
+	if (template_fd >= 0) {
+		close(template_fd);
+		ret = -EINVAL;
+		goto out_unlink;
+	}
+	ret = errno == ENOEXEC ? 0 : -errno;
+
+out_unlink:
+	unlink(path);
+	rmdir(dir);
+	return ret;
+}
+
+static int test_deny_write_while_template_alive(void)
+{
+	char dir[] = "/tmp/spawn-template-deny-write-XXXXXX";
+	char path[PATH_MAX];
+	int template_fd;
+	int write_fd;
+	int ret = 0;
+
+	if (!mkdtemp(dir))
+		return -errno;
+
+	snprintf(path, sizeof(path), "%s/copy", dir);
+	ret = copy_file(self_path, path);
+	if (ret)
+		goto out_unlink;
+
+	template_fd = create_template_path(path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_unlink;
+	}
+
+	write_fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC);
+	if (write_fd >= 0) {
+		close(write_fd);
+		ret = -EINVAL;
+	} else {
+		ret = errno == ETXTBSY ? 0 : -errno;
+	}
+
+	close(template_fd);
+out_unlink:
+	unlink(path);
+	rmdir(dir);
+	return ret;
+}
+
+static int test_stale_path_rejected(void)
+{
+	char dir[] = "/tmp/spawn-template-stale-XXXXXX";
+	char path[PATH_MAX];
+	char *const argv[] = { path, "--exit-zero", NULL };
+	int template_fd;
+	int ret = 0;
+
+	if (!mkdtemp(dir))
+		return -errno;
+
+	snprintf(path, sizeof(path), "%s/copy", dir);
+	ret = copy_file(self_path, path);
+	if (ret)
+		goto out_unlink;
+
+	template_fd = create_template_path(path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out_unlink;
+	}
+
+	if (chmod(path, 0600)) {
+		ret = -errno;
+		goto out_close_template;
+	}
+
+	ret = spawn_template(template_fd, argv, NULL, 0, 0);
+	if (ret >= 0)
+		ret = -EINVAL;
+	else
+		ret = ret == -ESTALE ? 0 : ret;
+
+out_close_template:
+	close(template_fd);
+out_unlink:
+	unlink(path);
+	rmdir(dir);
+	return ret;
+}
+
+static int test_path_replacement_allows_tool_update(void)
+{
+	char dir[] = "/tmp/spawn-template-update-XXXXXX";
+	char path[PATH_MAX];
+	char new_path[PATH_MAX];
+	char *const argv[] = { path, "--exit-zero", NULL };
+	int new_template_fd = -1;
+	int template_fd = -1;
+	int ret;
+
+	if (!mkdtemp(dir))
+		return -errno;
+
+	snprintf(path, sizeof(path), "%s/tool", dir);
+	snprintf(new_path, sizeof(new_path), "%s/tool.new", dir);
+	ret = copy_file(self_path, path);
+	if (ret)
+		goto out;
+	ret = copy_file(self_path, new_path);
+	if (ret)
+		goto out;
+
+	template_fd = create_template_path(path);
+	if (template_fd < 0) {
+		ret = -errno;
+		goto out;
+	}
+
+	if (rename(new_path, path)) {
+		ret = -errno;
+		goto out;
+	}
+
+	ret = spawn_template(template_fd, argv, NULL, 0, 0);
+	if (ret != -ESTALE) {
+		ret = ret < 0 ? ret : -EINVAL;
+		goto out;
+	}
+
+	new_template_fd = create_template_path(path);
+	if (new_template_fd < 0) {
+		ret = -errno;
+		goto out;
+	}
+
+	ret = spawn_template(new_template_fd, argv, NULL, 0, 0);
+
+out:
+	if (new_template_fd >= 0)
+		close(new_template_fd);
+	if (template_fd >= 0)
+		close(template_fd);
+	unlink(new_path);
+	unlink(path);
+	rmdir(dir);
+	return ret;
+}
+
+static void run_test(const char *name, int (*fn)(void))
+{
+	int ret = fn();
+
+	if (!ret)
+		ksft_test_result_pass("%s\n", name);
+	else
+		ksft_test_result_fail("%s failed: %s (%d)\n",
+				      name, strerror(-ret), -ret);
+}
+
+static void check_syscall_available(void)
+{
+	int template_fd;
+
+	template_fd = create_template_path(true_path);
+	if (template_fd >= 0) {
+		close(template_fd);
+		return;
+	}
+
+	if (errno == SPAWN_TEMPLATE_MISSING_SYSCALL_ERRNO)
+		ksft_exit_skip("spawn_template syscalls are not available\n");
+
+	ksft_exit_fail_msg("spawn_template_create failed: %s (%d)\n",
+			   strerror(errno), errno);
+}
+
+int main(int argc, char **argv)
+{
+	ssize_t len;
+
+	if (argc == 2 && !strcmp(argv[1], "--exit-zero"))
+		return 0;
+
+	if (argc == 3 && !strcmp(argv[1], "--check-fd-closed")) {
+		int fd = atoi(argv[2]);
+
+		return fcntl(fd, F_GETFD) < 0 && errno == EBADF ? 0 : 1;
+	}
+
+	if (argc == 3 && !strcmp(argv[1], "--check-fd-open")) {
+		int fd = atoi(argv[2]);
+
+		return fcntl(fd, F_GETFD) >= 0 ? 0 : 1;
+	}
+
+	if (argc == 4 && !strcmp(argv[1], "--check-fd-content"))
+		return read_fd_string(atoi(argv[2]), argv[3]) ? 1 : 0;
+
+	if (argc == 3 && !strcmp(argv[1], "--check-cwd")) {
+		char cwd[PATH_MAX];
+
+		if (!getcwd(cwd, sizeof(cwd)))
+			return 1;
+		return strcmp(cwd, argv[2]) ? 1 : 0;
+	}
+
+	if (argc == 3 && !strcmp(argv[1], "--check-sigmask")) {
+		sigset_t mask;
+		int sig = atoi(argv[2]);
+
+		if (sigprocmask(SIG_BLOCK, NULL, &mask))
+			return 1;
+		return sigismember(&mask, sig) == 1 ? 0 : 1;
+	}
+
+	if (argc == 3 && !strcmp(argv[1], "--check-sigdefault")) {
+		struct sigaction sa;
+		int sig = atoi(argv[2]);
+
+		if (sigaction(sig, NULL, &sa))
+			return 1;
+		return sa.sa_handler == SIG_DFL ? 0 : 1;
+	}
+
+	if (argc == 2 && !strcmp(argv[1], "--write-stdio")) {
+		if (write(STDOUT_FILENO, "stdout-token\n", 13) != 13)
+			return 1;
+		if (write(STDERR_FILENO, "stderr-token\n", 13) != 13)
+			return 1;
+		return 0;
+	}
+
+	true_path = find_true();
+	if (!true_path)
+		ksft_exit_skip("could not find true executable\n");
+
+	len = readlink("/proc/self/exe", self_path, sizeof(self_path) - 1);
+	if (len < 0)
+		ksft_exit_fail_msg("readlink(/proc/self/exe) failed: %s\n",
+				   strerror(errno));
+	self_path[len] = '\0';
+
+	check_syscall_available();
+
+	ksft_print_header();
+	ksft_set_plan(17);
+
+	run_test("basic spawn", test_basic_spawn);
+	run_test("relative path rejected", test_relative_path_rejected);
+	run_test("execfd execute permission checked",
+		 test_execfd_requires_execute);
+	run_test("default fd close", test_default_closes_extra_fds);
+	run_test("close_range action max fd", test_close_range_max_action);
+	run_test("dup2 stdio actions", test_dup2_stdio_actions);
+	run_test("open action stdin", test_open_action_stdin);
+	run_test("fchdir action", test_fchdir_action);
+	run_test("sigmask action", test_sigmask_action);
+	run_test("sigdefault action", test_sigdefault_action);
+	run_test("inherit fds flag", test_inherit_fds_flag);
+	run_test("pidfd waitid", test_pidfd_waitid);
+	run_test("create-time actions rejected", test_create_actions_rejected);
+	run_test("script template unsupported", test_script_template_unsupported);
+	run_test("deny write while template alive",
+		 test_deny_write_while_template_alive);
+	run_test("stale path rejected", test_stale_path_rejected);
+	run_test("path replacement allows tool update",
+		 test_path_replacement_allows_tool_update);
+
+	ksft_finished();
+}
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 12/13] syscalls: add generic spawn template entries
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Add spawn_template_create() and spawn_template_spawn() to the generic
syscall table and asm-generic UAPI numbering. This lets architectures
using the generic table pick up the spawn-template ABI instead of
leaving the mechanism x86-only.

Signed-off-by: Li Chen <me@linux.beauty>
---
 arch/x86/entry/syscalls/syscall_64.tbl | 2 ++
 include/uapi/asm-generic/unistd.h      | 7 ++++++-
 scripts/syscall.tbl                    | 2 ++
 3 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index d6c1667e8f3b8..e9dcfc6de79bc 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -396,6 +396,8 @@
 469	common	file_setattr		sys_file_setattr
 470	common	listns			sys_listns
 471	common	rseq_slice_yield	sys_rseq_slice_yield
+472	64	spawn_template_create	sys_spawn_template_create
+473	64	spawn_template_spawn	sys_spawn_template_spawn
 #
 # Due to a historical design error, certain syscalls are numbered differently
 # in x32 as compared to native x86_64.  These syscalls have numbers 512-547.
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index a627acc8fb5fe..8589f2b9696a7 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -863,8 +863,13 @@ __SYSCALL(__NR_listns, sys_listns)
 #define __NR_rseq_slice_yield 471
 __SYSCALL(__NR_rseq_slice_yield, sys_rseq_slice_yield)
 
+#define __NR_spawn_template_create 472
+__SYSCALL(__NR_spawn_template_create, sys_spawn_template_create)
+#define __NR_spawn_template_spawn 473
+__SYSCALL(__NR_spawn_template_spawn, sys_spawn_template_spawn)
+
 #undef __NR_syscalls
-#define __NR_syscalls 472
+#define __NR_syscalls 474
 
 /*
  * 32 bit systems traditionally used different
diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl
index 7a42b32b65776..7f8e74e866e48 100644
--- a/scripts/syscall.tbl
+++ b/scripts/syscall.tbl
@@ -412,3 +412,5 @@
 469	common	file_setattr			sys_file_setattr
 470	common	listns				sys_listns
 471	common	rseq_slice_yield		sys_rseq_slice_yield
+472	common	spawn_template_create		sys_spawn_template_create
+473	common	spawn_template_spawn		sys_spawn_template_spawn
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 11/13] exec: let close-range actions target the max fd
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Allow CLOSE_RANGE actions to pass newfd == -1 to mean the largest
possible fd. This gives userspace a compact way to request the common
close_range(first, ~0U, flags) pattern even though the UAPI action uses
signed fd fields so OPEN actions can still carry AT_FDCWD.

Signed-off-by: Li Chen <me@linux.beauty>
---
 Documentation/userspace-api/spawn_template.rst |  3 ++-
 fs/spawn_template.c                            | 10 +++++++---
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/Documentation/userspace-api/spawn_template.rst b/Documentation/userspace-api/spawn_template.rst
index afe215e51db6f..be66be20d4fde 100644
--- a/Documentation/userspace-api/spawn_template.rst
+++ b/Documentation/userspace-api/spawn_template.rst
@@ -86,7 +86,8 @@ kind of setup that ``posix_spawn_file_actions_t`` commonly performs:
   Open a path using ``struct open_how`` and install it at ``newfd``.
 
 ``SPAWN_TEMPLATE_ACTION_CLOSE_RANGE``
-  Apply ``close_range()`` to a child fd range.
+  Apply ``close_range()`` to a child fd range.  Passing ``newfd == -1`` means
+  the range extends to the largest possible fd.
 
 ``SPAWN_TEMPLATE_ACTION_SIGMASK``
   Set the child signal mask.
diff --git a/fs/spawn_template.c b/fs/spawn_template.c
index 6430a6645fb57..82b833bc9865a 100644
--- a/fs/spawn_template.c
+++ b/fs/spawn_template.c
@@ -220,6 +220,8 @@ static int spawn_template_apply_sigdefault(const struct spawn_template_action *a
 
 static int spawn_template_apply_action(const struct spawn_template_action *action)
 {
+	unsigned int max_fd;
+
 	switch (action->type) {
 	case SPAWN_TEMPLATE_ACTION_CLOSE:
 		return close_fd(action->fd);
@@ -251,7 +253,8 @@ static int spawn_template_apply_action(const struct spawn_template_action *actio
 	case SPAWN_TEMPLATE_ACTION_OPEN:
 		return spawn_template_apply_open(action);
 	case SPAWN_TEMPLATE_ACTION_CLOSE_RANGE:
-		return do_close_range(action->fd, action->newfd, action->flags);
+		max_fd = action->newfd == -1 ? ~0U : action->newfd;
+		return do_close_range(action->fd, max_fd, action->flags);
 	case SPAWN_TEMPLATE_ACTION_SIGMASK:
 		return spawn_template_apply_sigmask(action);
 	case SPAWN_TEMPLATE_ACTION_SIGDEFAULT:
@@ -306,8 +309,9 @@ static int spawn_template_copy_actions(struct spawn_template_action **out_action
 				return -EINVAL;
 			break;
 		case SPAWN_TEMPLATE_ACTION_CLOSE_RANGE:
-			if (actions[i].fd < 0 || actions[i].newfd < 0 ||
-			    actions[i].fd > actions[i].newfd ||
+			if (actions[i].fd < 0 || actions[i].newfd < -1 ||
+			    (actions[i].newfd >= 0 &&
+			     actions[i].fd > actions[i].newfd) ||
 			    (actions[i].flags &
 			     ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC)) ||
 			    actions[i].arg)
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 10/13] exec: require absolute paths for path-created templates
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Path-created spawn templates re-open the stored path during spawn-time
revalidation. A relative path would be interpreted against the caller cwd
at spawn time, not necessarily the cwd used when the template was created.

Reject relative paths for now. Userspace can resolve the executable first
or create the template from an executable fd when it needs cwd-relative
lookup.

Signed-off-by: Li Chen <me@linux.beauty>
---
 Documentation/userspace-api/spawn_template.rst | 17 ++++++++++++++---
 fs/spawn_template.c                            |  2 ++
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/Documentation/userspace-api/spawn_template.rst b/Documentation/userspace-api/spawn_template.rst
index 0396d292fd17d..afe215e51db6f 100644
--- a/Documentation/userspace-api/spawn_template.rst
+++ b/Documentation/userspace-api/spawn_template.rst
@@ -30,9 +30,20 @@ returns a template fd.  The fd is an ordinary file descriptor backed by an
 anonymous inode.  Closing the fd releases the template.
 
 Userspace can identify the executable either by an existing executable fd or by
-path.  Exactly one of ``execfd`` and ``filename`` must be supplied.  Passing
-``SPAWN_TEMPLATE_CREATE_CLOEXEC`` sets ``O_CLOEXEC`` on the returned template
-fd.
+an absolute path.  Exactly one of ``execfd`` and ``filename`` must be supplied.
+Passing ``SPAWN_TEMPLATE_CREATE_CLOEXEC`` sets ``O_CLOEXEC`` on the returned
+template fd.
+
+Relative paths are rejected for path-created templates.  The kernel stores the
+filename and re-opens it at spawn time to check that the path still names the
+same executable.  A relative filename would be resolved against the caller's
+current working directory at spawn time, not the directory that was current
+when the template was created.  For example, a template created for ``bin/tool``
+while the caller is in ``/repo-a`` could later be spawned after the caller has
+changed to ``/repo-b``.  Revalidating ``bin/tool`` would then look under
+``/repo-b`` and give different semantics from the executable that was
+originally templated.  Userspace that wants directory-relative lookup should
+open the executable itself and create the template from ``execfd``.
 
 Creating a template for an unsupported executable format fails.  For this RFC
 that means non-ELF executables fail template creation rather than becoming a
diff --git a/fs/spawn_template.c b/fs/spawn_template.c
index a11a7ed676416..6430a6645fb57 100644
--- a/fs/spawn_template.c
+++ b/fs/spawn_template.c
@@ -441,6 +441,8 @@ static int spawn_template_open_filename(u64 filename, struct file **file,
 	tmp = strndup_user(u64_to_user_ptr(filename), PATH_MAX);
 	if (IS_ERR(tmp))
 		return PTR_ERR(tmp);
+	if (tmp[0] != '/')
+		return -EINVAL;
 	kfilename = tmp;
 
 	tmp_file = open_exec(kfilename);
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 09/13] Documentation: describe spawn templates
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Document the spawn_template userspace ABI, fd lifetime, per-spawn
actions, default fd-closing behavior, security model, invalidation, and
cached ELF metadata. Keep workload-specific benchmark details out of the
kernel documentation.

Add the spawn template files to the exec/binfmt MAINTAINERS entry so the
documentation, UAPI, internal header, and implementation are covered in
the same patch.

Signed-off-by: Li Chen <me@linux.beauty>
---
 Documentation/userspace-api/index.rst         |   1 +
 .../userspace-api/spawn_template.rst          | 141 ++++++++++++++++++
 MAINTAINERS                                   |   2 +
 3 files changed, 144 insertions(+)
 create mode 100644 Documentation/userspace-api/spawn_template.rst

diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index a68b1bea57a85..28520d16d3862 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -22,6 +22,7 @@ System calls
    ioctl/index
    mseal
    rseq
+   spawn_template
 
 Security-related interfaces
 ===========================
diff --git a/Documentation/userspace-api/spawn_template.rst b/Documentation/userspace-api/spawn_template.rst
new file mode 100644
index 0000000000000..0396d292fd17d
--- /dev/null
+++ b/Documentation/userspace-api/spawn_template.rst
@@ -0,0 +1,141 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+Spawn templates
+===============
+
+``spawn_template`` is a userspace-controlled interface for workloads that
+repeatedly start the same executable with different arguments, environment, and
+file-descriptor setup.
+
+Userspace creates a template fd for an executable with
+``spawn_template_create()``.  Later calls to ``spawn_template_spawn()`` create a
+new child from that template and return both a pid and a pidfd.  The child still
+executes through the normal ``execve`` path.  The template only lets the kernel
+reuse metadata that is safe to reuse after revalidation.
+
+This is intended for launchers, shells, and agent runtimes that already know
+which tools are hot.  The kernel does not decide policy for names such as
+``rg``, ``git``, or ``sed``.  Userspace should keep its existing spawn path as a
+fallback for unsupported files, invalidated templates, and policy decisions.
+
+This RFC version supports ELF executable templates only.  Scripts, binfmt_misc
+targets, and other non-ELF formats are expected to use the fallback path.
+
+Template lifetime
+=================
+
+``spawn_template_create()`` takes ``struct spawn_template_create_args`` and
+returns a template fd.  The fd is an ordinary file descriptor backed by an
+anonymous inode.  Closing the fd releases the template.
+
+Userspace can identify the executable either by an existing executable fd or by
+path.  Exactly one of ``execfd`` and ``filename`` must be supplied.  Passing
+``SPAWN_TEMPLATE_CREATE_CLOEXEC`` sets ``O_CLOEXEC`` on the returned template
+fd.
+
+Creating a template for an unsupported executable format fails.  For this RFC
+that means non-ELF executables fail template creation rather than becoming a
+partially cached template.
+
+Create-time fd actions are not supported.  ``actions`` and ``actions_len`` in
+``struct spawn_template_create_args`` are reserved and must be zero.  File
+descriptor numbers are per-process state, so reusable fd actions would be
+ambiguous once the creating process changes its fd table.
+
+Spawning
+========
+
+``spawn_template_spawn()`` takes a template fd and
+``struct spawn_template_spawn_args``.  ``argv`` and ``envp`` point to the normal
+userspace argument and environment vectors for the new image.  ``pidfd`` points
+to an ``int`` in userspace where the kernel stores the new pidfd.  The syscall
+return value is the new pid on success.
+
+A successful ``spawn_template_spawn()`` return means the child has been created
+and the pidfd has been installed.  After that point, per-spawn action failures
+or exec failures are reported by the child exit status, not by changing the
+syscall return value.  The syscall itself returns a negative errno only for
+errors detected before child creation, such as bad arguments, a bad template
+fd, stale executable identity, or clone failure.
+
+Per-spawn actions run in the child before exec.  They are intended for the same
+kind of setup that ``posix_spawn_file_actions_t`` commonly performs:
+
+``SPAWN_TEMPLATE_ACTION_CLOSE``
+  Close one fd.
+
+``SPAWN_TEMPLATE_ACTION_DUP2``
+  Duplicate one fd to another fd, optionally with ``O_CLOEXEC``.
+
+``SPAWN_TEMPLATE_ACTION_FCHDIR``
+  Change the child's current working directory to an open directory fd.
+
+``SPAWN_TEMPLATE_ACTION_OPEN``
+  Open a path using ``struct open_how`` and install it at ``newfd``.
+
+``SPAWN_TEMPLATE_ACTION_CLOSE_RANGE``
+  Apply ``close_range()`` to a child fd range.
+
+``SPAWN_TEMPLATE_ACTION_SIGMASK``
+  Set the child signal mask.
+
+``SPAWN_TEMPLATE_ACTION_SIGDEFAULT``
+  Reset selected signal dispositions to ``SIG_DFL``.
+
+By default, the child closes all inherited file descriptors above standard
+error after the requested actions have run.  Passing
+``SPAWN_TEMPLATE_SPAWN_INHERIT_FDS`` keeps the traditional inheritance model.
+Launchers for untrusted or secret-bearing workloads should prefer the default.
+
+Security model
+==============
+
+``spawn_template_spawn()`` is not a shortcut around ``execve`` security.  Each
+spawn still reaches the normal binary handler and credential commit path, so
+permission checks, LSM hooks, secure-exec handling, and ``no_new_privs`` remain
+part of execution.
+
+The template fd does not grant ambient authority to unrelated tasks.  The
+current implementation requires the caller to have the same credential object
+that created the template.  Passing the fd with ``SCM_RIGHTS`` is therefore not
+enough to delegate spawn authority after credentials have changed.
+
+The kernel pins the executable inode against writes while the template exists.
+An in-place writer therefore fails while a template fd is alive.  A package
+manager can still replace a tool with a rename; a path-created template then
+sees that the absolute path resolves to a different executable and spawn fails
+before creating a child.  Userspace can close the old template fd and create a
+new one after such an update.
+
+Each spawn revalidates cached identity metadata before using template metadata.
+The key includes device, inode, size, mode, owner, ctime, and mtime.
+Path-created templates re-open the path before child creation and reject reuse
+if the path now names a different executable.
+
+Cached metadata
+===============
+
+For ELF executables, the template caches only the main executable ELF header,
+program headers, and executable identity key.  The cached program headers are
+used to avoid repeated metadata reads for hot executables after the executable
+identity has been revalidated.
+
+The cache does not include the shared-library dependency graph.  Shared
+libraries are found by the userspace dynamic linker after exec and depend on
+userspace policy such as ``LD_LIBRARY_PATH``, ``RPATH``, ``RUNPATH``,
+``/etc/ld.so.cache``, mount namespaces, and secure-exec state.  The kernel
+therefore does not try to duplicate dynamic-linker policy in a spawn template.
+
+Errors and fallback
+===================
+
+If template creation reports an unsupported format, or if spawn reports a stale
+template before child creation, the caller should use its existing spawn
+implementation.  A launcher may also drop the template fd and create a new
+template after a failure.  Once spawn has returned a pid, the caller should
+observe child success or failure by waiting on the pid or pidfd.
+
+The interface is designed so ordinary tools do not need to be modified.
+Runtimes that already centralize process launch can opt in one executable at a
+time and preserve their existing fallback behavior.
diff --git a/MAINTAINERS b/MAINTAINERS
index ea4134a188779..3e737097940f9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9728,7 +9728,9 @@ M:	Kees Cook <kees@kernel.org>
 L:	linux-mm@kvack.org
 S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/execve
+F:	arch/x86/entry/syscalls/syscall_64.tbl
 F:	Documentation/userspace-api/ELF.rst
+F:	Documentation/userspace-api/spawn_template.rst
 F:	fs/*binfmt_*.c
 F:	fs/Kconfig.binfmt
 F:	fs/exec.c
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 08/13] binfmt_elf: cache ELF metadata for spawn templates
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Spawn templates keep an opened executable and revalidate its file identity
before every spawn. Add an ELF-side template object for the main
executable.
It caches the executable identity key, ELF header, program header table,
and program header count so repeated spawns can reuse validated metadata.
Do not cache interpreter metadata, shared-library dependency state, or
derived mapping-layout state in this RFC.
Keep the normal exec security path intact. The child still executes through
bprm_execve(), credentials, permissions, and LSM hooks. This only avoids
rereading immutable main-executable metadata after template creation and
revalidation.

Signed-off-by: Li Chen <me@linux.beauty>
---
 fs/binfmt_elf.c                | 104 ++++++++++++++++++++++++++++++++-
 fs/exec.c                      |  37 +++++++++++-
 fs/spawn_template.c            |  38 +++++++-----
 include/linux/binfmts.h        |   6 ++
 include/linux/spawn_template.h |  47 +++++++++++++++
 5 files changed, 213 insertions(+), 19 deletions(-)

diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index 16a56b6b3f6ca..631dd029aeee7 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -48,6 +48,7 @@
 #include <linux/uaccess.h>
 #include <uapi/linux/rseq.h>
 #include <linux/rseq.h>
+#include <linux/spawn_template.h>
 #include <asm/param.h>
 #include <asm/page.h>
 
@@ -552,6 +553,89 @@ static struct elf_phdr *load_elf_phdrs(const struct elfhdr *elf_ex,
 	return elf_phdata;
 }
 
+#if !ELF_COMPAT
+void spawn_exec_template_put(struct spawn_exec_template *tmpl)
+{
+	if (!tmpl)
+		return;
+	if (!refcount_dec_and_test(&tmpl->refcount))
+		return;
+	kfree(tmpl->exec_phdrs);
+	kfree(tmpl);
+}
+
+struct spawn_exec_template *
+spawn_exec_template_get(struct spawn_exec_template *tmpl)
+{
+	refcount_inc(&tmpl->refcount);
+	return tmpl;
+}
+
+bool spawn_exec_template_matches(struct spawn_exec_template *tmpl,
+				 struct file *file)
+{
+	if (!tmpl)
+		return false;
+	if (!spawn_template_file_key_matches(file, &tmpl->exec_key))
+		return false;
+	if (!can_mmap_file(file))
+		return false;
+	return true;
+}
+
+int spawn_exec_template_create(struct file *file,
+			       struct spawn_exec_template **out)
+{
+	struct spawn_exec_template *tmpl;
+	loff_t pos = 0;
+	ssize_t nread;
+	int retval;
+
+	*out = NULL;
+
+	tmpl = kzalloc_obj(*tmpl, GFP_KERNEL);
+	if (!tmpl)
+		return -ENOMEM;
+	refcount_set(&tmpl->refcount, 1);
+
+	spawn_template_fill_file_key(file, &tmpl->exec_key);
+
+	nread = kernel_read(file, &tmpl->exec_ehdr, sizeof(tmpl->exec_ehdr),
+			    &pos);
+	if (nread < 0) {
+		retval = nread;
+		goto out_put_template;
+	}
+
+	retval = -ENOEXEC;
+	if (nread != sizeof(tmpl->exec_ehdr))
+		goto out_put_template;
+	if (memcmp(tmpl->exec_ehdr.e_ident, ELFMAG, SELFMAG) != 0)
+		goto out_put_template;
+	if (tmpl->exec_ehdr.e_type != ET_EXEC &&
+	    tmpl->exec_ehdr.e_type != ET_DYN)
+		goto out_put_template;
+	if (!elf_check_arch(&tmpl->exec_ehdr))
+		goto out_put_template;
+	if (elf_check_fdpic(&tmpl->exec_ehdr))
+		goto out_put_template;
+	if (!can_mmap_file(file))
+		goto out_put_template;
+
+	tmpl->exec_phdrs = load_elf_phdrs(&tmpl->exec_ehdr, file);
+	if (!tmpl->exec_phdrs)
+		goto out_put_template;
+	tmpl->exec_phnum = tmpl->exec_ehdr.e_phnum;
+
+	*out = tmpl;
+	return 0;
+
+out_put_template:
+	spawn_exec_template_put(tmpl);
+	return retval;
+}
+#endif
+
 #ifndef CONFIG_ARCH_BINFMT_ELF_STATE
 
 /**
@@ -832,6 +916,7 @@ static int parse_elf_properties(struct file *f, const struct elf_phdr *phdr,
 static int load_elf_binary(struct linux_binprm *bprm)
 {
 	struct file *interpreter = NULL; /* to shut gcc up */
+	struct spawn_exec_template *spawn_tmpl = bprm->spawn_template;
 	unsigned long load_bias = 0, phdr_addr = 0;
 	int first_pt_load = 1;
 	unsigned long error;
@@ -851,6 +936,12 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE;
 	struct mm_struct *mm;
 	struct pt_regs *regs;
+	bool use_spawn_tmpl = spawn_exec_template_matches(spawn_tmpl, bprm->file);
+	bool free_elf_phdata = true;
+
+	if (use_spawn_tmpl)
+		memcpy(bprm->buf, &spawn_tmpl->exec_ehdr,
+		       sizeof(spawn_tmpl->exec_ehdr));
 
 	retval = -ENOEXEC;
 	/* First of all, some simple consistency checks */
@@ -866,7 +957,12 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	if (!can_mmap_file(bprm->file))
 		goto out;
 
-	elf_phdata = load_elf_phdrs(elf_ex, bprm->file);
+	if (use_spawn_tmpl)
+		elf_phdata = spawn_tmpl->exec_phdrs;
+	else
+		elf_phdata = load_elf_phdrs(elf_ex, bprm->file);
+	if (use_spawn_tmpl)
+		free_elf_phdata = false;
 	if (!elf_phdata)
 		goto out;
 
@@ -1283,7 +1379,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
 		}
 	}
 
-	kfree(elf_phdata);
+	if (free_elf_phdata)
+		kfree(elf_phdata);
 
 	set_binfmt(&elf_format);
 
@@ -1390,7 +1487,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	if (interpreter)
 		fput(interpreter);
 out_free_ph:
-	kfree(elf_phdata);
+	if (free_elf_phdata)
+		kfree(elf_phdata);
 	goto out;
 }
 
diff --git a/fs/exec.c b/fs/exec.c
index 5b91a9b208a77..96b6f6274e0d3 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1914,9 +1914,12 @@ static inline struct user_arg_ptr native_arg(const char __user *const __user *p)
 	return (struct user_arg_ptr){.ptr.native = p};
 }
 
-static int do_execveat_file_common(struct file *file, struct filename *filename,
-				   struct user_arg_ptr argv,
-				   struct user_arg_ptr envp, int flags)
+static int do_execveat_file_template_common(struct file *file,
+					    struct filename *filename,
+					    struct user_arg_ptr argv,
+					    struct user_arg_ptr envp,
+					    int flags,
+					    struct spawn_exec_template *tmpl)
 {
 	struct linux_binprm *bprm;
 	struct file *exec_file;
@@ -1940,11 +1943,20 @@ static int do_execveat_file_common(struct file *file, struct filename *filename,
 	if (IS_ERR(bprm))
 		return PTR_ERR(bprm);
 
+	bprm->spawn_template = tmpl;
 	retval = do_execveat_common_bprm(bprm, argv, envp);
 	free_bprm(bprm);
 	return retval;
 }
 
+static int do_execveat_file_common(struct file *file, struct filename *filename,
+				   struct user_arg_ptr argv,
+				   struct user_arg_ptr envp, int flags)
+{
+	return do_execveat_file_template_common(file, filename, argv, envp,
+						flags, NULL);
+}
+
 int kernel_execveat_file(struct file *file, const char *filename,
 			 const void __user *argv,
 			 const void __user *envp,
@@ -1962,6 +1974,25 @@ int kernel_execveat_file(struct file *file, const char *filename,
 				       native_arg(user_envp), flags);
 }
 
+int kernel_execveat_file_template(struct file *file, const char *filename,
+				  const void __user *argv,
+				  const void __user *envp, int flags,
+				  struct spawn_exec_template *tmpl)
+{
+	const char __user *const __user *user_argv;
+	const char __user *const __user *user_envp;
+
+	CLASS(filename_kernel, name)(filename);
+
+	user_argv = (const char __user *const __user *)argv;
+	user_envp = (const char __user *const __user *)envp;
+
+	return do_execveat_file_template_common(file, name,
+						native_arg(user_argv),
+						native_arg(user_envp),
+						flags, tmpl);
+}
+
 void set_binfmt(struct linux_binfmt *new)
 {
 	struct mm_struct *mm = current->mm;
diff --git a/fs/spawn_template.c b/fs/spawn_template.c
index 268f804227987..a11a7ed676416 100644
--- a/fs/spawn_template.c
+++ b/fs/spawn_template.c
@@ -28,7 +28,7 @@
 
 struct spawn_template {
 	struct file *exec_file;
-	struct spawn_template_file_key exec_key;
+	struct spawn_exec_template *exec_template;
 	const struct cred *creator_cred;
 	char *filename;
 	bool deny_write;
@@ -36,6 +36,7 @@ struct spawn_template {
 
 struct spawn_template_spawn_context {
 	struct spawn_template *tmpl;
+	struct spawn_exec_template *exec_template;
 	struct spawn_template_spawn_args args;
 	struct spawn_template_action *actions;
 };
@@ -114,16 +115,16 @@ static bool spawn_template_key_matches(struct spawn_template *tmpl)
 		file = tmp;
 
 		matches = spawn_template_file_key_matches(file,
-							  &tmpl->exec_key);
+				&tmpl->exec_template->exec_key);
 		matches = matches && spawn_template_file_exec_allowed(file);
 		exe_file_allow_write_access(file);
 		if (!matches)
 			return false;
 	}
 
-	return spawn_template_file_exec_allowed(tmpl->exec_file) &&
-	       spawn_template_file_key_matches(tmpl->exec_file,
-					       &tmpl->exec_key);
+	if (!spawn_template_file_exec_allowed(tmpl->exec_file))
+		return false;
+	return spawn_exec_template_matches(tmpl->exec_template, tmpl->exec_file);
 }
 
 static int spawn_template_copy_signal_set(const struct spawn_template_action *action,
@@ -331,26 +332,29 @@ static int spawn_template_child(void *data)
 {
 	struct spawn_template_spawn_context *ctx = data;
 	struct spawn_template *tmpl = ctx->tmpl;
+	struct spawn_exec_template *exec_template = ctx->exec_template;
 	int ret;
 	u64 i;
 
 	for (i = 0; i < ctx->args.actions_len; i++) {
 		ret = spawn_template_apply_action(&ctx->actions[i]);
 		if (ret < 0)
-			goto out_exec_error;
+			goto out_put_exec_template;
 	}
 
 	if (!(ctx->args.flags & SPAWN_TEMPLATE_SPAWN_INHERIT_FDS)) {
 		ret = do_close_range(3, ~0U, 0);
 		if (ret < 0)
-			goto out_exec_error;
+			goto out_put_exec_template;
 	}
 
-	ret = kernel_execveat_file(tmpl->exec_file, "",
-				   u64_to_user_ptr(ctx->args.argv),
-				   u64_to_user_ptr(ctx->args.envp),
-				   AT_EMPTY_PATH);
-out_exec_error:
+	ret = kernel_execveat_file_template(tmpl->exec_file, "",
+					    u64_to_user_ptr(ctx->args.argv),
+					    u64_to_user_ptr(ctx->args.envp),
+					    AT_EMPTY_PATH,
+					    exec_template);
+out_put_exec_template:
+	spawn_exec_template_put(exec_template);
 	if (ret < 0)
 		do_exit(spawn_template_exit_status(ret));
 	return 0;
@@ -373,6 +377,7 @@ static int spawn_template_release(struct inode *inode, struct file *file)
 
 	if (tmpl->deny_write)
 		exe_file_allow_write_access(tmpl->exec_file);
+	spawn_exec_template_put(tmpl->exec_template);
 	fput(tmpl->exec_file);
 	put_cred(tmpl->creator_cred);
 	kfree(tmpl->filename);
@@ -501,7 +506,10 @@ SYSCALL_DEFINE2(spawn_template_create,
 						 &tmpl->deny_write);
 	if (ret)
 		goto out_free_tmpl;
-	spawn_template_fill_file_key(tmpl->exec_file, &tmpl->exec_key);
+
+	ret = spawn_exec_template_create(tmpl->exec_file, &tmpl->exec_template);
+	if (ret)
+		goto out_put_exec;
 
 	if (args.flags & SPAWN_TEMPLATE_CREATE_CLOEXEC)
 		fd_flags |= O_CLOEXEC;
@@ -514,6 +522,7 @@ SYSCALL_DEFINE2(spawn_template_create,
 	return ret;
 
 out_put_exec:
+	spawn_exec_template_put(tmpl->exec_template);
 	if (tmpl->deny_write)
 		exe_file_allow_write_access(tmpl->exec_file);
 	fput(tmpl->exec_file);
@@ -580,6 +589,7 @@ SYSCALL_DEFINE3(spawn_template_spawn, int, template_fd,
 		ret = -ESTALE;
 		goto out_free_actions;
 	}
+	ctx->exec_template = spawn_exec_template_get(ctx->tmpl->exec_template);
 
 	kargs = (struct kernel_clone_args) {
 		.flags		= CLONE_VM | CLONE_VFORK | CLONE_PIDFD,
@@ -590,6 +600,8 @@ SYSCALL_DEFINE3(spawn_template_spawn, int, template_fd,
 	};
 
 	ret = kernel_clone(&kargs);
+	if (ret < 0)
+		spawn_exec_template_put(ctx->exec_template);
 
 out_free_actions:
 	kfree(ctx->actions);
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index c0715678c9a06..4e76a94d331a8 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -9,6 +9,7 @@
 
 struct filename;
 struct coredump_params;
+struct spawn_exec_template;
 
 #define CORENAME_MAX_SIZE 128
 
@@ -53,6 +54,7 @@ struct linux_binprm {
 	struct file *executable; /* Executable to pass to the interpreter */
 	struct file *interpreter;
 	struct file *file;
+	struct spawn_exec_template *spawn_template;
 	struct cred *cred;	/* new credentials */
 	int unsafe;		/* how unsafe this exec is (mask of LSM_UNSAFE_*) */
 	unsigned int per_clear;	/* bits to clear in current->personality */
@@ -145,6 +147,10 @@ int kernel_execveat_file(struct file *file, const char *filename,
 			 const void __user *argv,
 			 const void __user *envp,
 			 int flags);
+int kernel_execveat_file_template(struct file *file, const char *filename,
+				  const void __user *argv,
+				  const void __user *envp, int flags,
+				  struct spawn_exec_template *tmpl);
 extern void set_binfmt(struct linux_binfmt *new);
 extern ssize_t read_code(struct file *, unsigned long, loff_t, size_t);
 
diff --git a/include/linux/spawn_template.h b/include/linux/spawn_template.h
index f14a7749fe55b..426413bc11eea 100644
--- a/include/linux/spawn_template.h
+++ b/include/linux/spawn_template.h
@@ -2,7 +2,9 @@
 #ifndef _LINUX_SPAWN_TEMPLATE_H
 #define _LINUX_SPAWN_TEMPLATE_H
 
+#include <linux/elf.h>
 #include <linux/fs.h>
+#include <linux/refcount.h>
 
 struct spawn_template_file_key {
 	dev_t dev;
@@ -17,9 +19,54 @@ struct spawn_template_file_key {
 	u64 mtime_nsec;
 };
 
+struct spawn_exec_template {
+	refcount_t refcount;
+	struct spawn_template_file_key exec_key;
+	struct elfhdr exec_ehdr;
+	struct elf_phdr *exec_phdrs;
+	unsigned int exec_phnum;
+};
+
 void spawn_template_fill_file_key(struct file *file,
 				  struct spawn_template_file_key *key);
 bool spawn_template_file_key_matches(struct file *file,
 				     const struct spawn_template_file_key *key);
 
+#ifdef CONFIG_BINFMT_ELF
+int spawn_exec_template_create(struct file *file,
+			       struct spawn_exec_template **out);
+struct spawn_exec_template *
+spawn_exec_template_get(struct spawn_exec_template *tmpl);
+void spawn_exec_template_put(struct spawn_exec_template *tmpl);
+bool spawn_exec_template_matches(struct spawn_exec_template *tmpl,
+				 struct file *file);
+#else
+static inline int spawn_exec_template_create(struct file *file,
+					     struct spawn_exec_template **out)
+{
+	(void)file;
+	(void)out;
+	return -ENOEXEC;
+}
+
+static inline void spawn_exec_template_put(struct spawn_exec_template *tmpl)
+{
+	(void)tmpl;
+}
+
+static inline struct spawn_exec_template *
+spawn_exec_template_get(struct spawn_exec_template *tmpl)
+{
+	return tmpl;
+}
+
+static inline bool spawn_exec_template_matches(struct spawn_exec_template *tmpl,
+					       struct file *file)
+{
+	(void)tmpl;
+	(void)file;
+	return false;
+}
+#endif
+
 #endif /* _LINUX_SPAWN_TEMPLATE_H */
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 07/13] exec: validate spawn template executable identity
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Record a conservative executable identity key when a template is created:
device, inode, size, mode, owner, ctime, and mtime. Recheck it before
each spawn. For path-created templates, also reopen the path so a replaced
executable cannot silently reuse the old template fd.
Reject stale templates with ESTALE. Keep the check conservative by also
rechecking that the file remains a regular executable mapping target.

Signed-off-by: Li Chen <me@linux.beauty>
---
 MAINTAINERS                    |  1 +
 fs/spawn_template.c            | 75 ++++++++++++++++++++++++++++++++++
 include/linux/spawn_template.h | 25 ++++++++++++
 3 files changed, 101 insertions(+)
 create mode 100644 include/linux/spawn_template.h

diff --git a/MAINTAINERS b/MAINTAINERS
index d5441812825c3..ea4134a188779 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9737,6 +9737,7 @@ F:	fs/tests/binfmt_*_kunit.c
 F:	fs/tests/exec_kunit.c
 F:	include/linux/binfmts.h
 F:	include/linux/elf.h
+F:	include/linux/spawn_template.h
 F:	include/uapi/linux/auxvec.h
 F:	include/uapi/linux/binfmts.h
 F:	include/uapi/linux/elf.h
diff --git a/fs/spawn_template.c b/fs/spawn_template.c
index 8c3711929cffb..268f804227987 100644
--- a/fs/spawn_template.c
+++ b/fs/spawn_template.c
@@ -15,6 +15,7 @@
 #include <linux/sched/task.h>
 #include <linux/signal.h>
 #include <linux/slab.h>
+#include <linux/spawn_template.h>
 #include <linux/string.h>
 #include <linux/syscalls.h>
 #include <linux/uaccess.h>
@@ -27,6 +28,7 @@
 
 struct spawn_template {
 	struct file *exec_file;
+	struct spawn_template_file_key exec_key;
 	const struct cred *creator_cred;
 	char *filename;
 	bool deny_write;
@@ -40,6 +42,46 @@ struct spawn_template_spawn_context {
 
 static const struct file_operations spawn_template_fops;
 
+static bool spawn_template_file_exec_allowed(struct file *file);
+
+void spawn_template_fill_file_key(struct file *file,
+				  struct spawn_template_file_key *key)
+{
+	struct inode *inode = file_inode(file);
+	struct timespec64 ctime = inode_get_ctime(inode);
+	struct timespec64 mtime = inode_get_mtime(inode);
+
+	key->dev = inode->i_sb->s_dev;
+	key->ino = inode->i_ino;
+	key->size = i_size_read(inode);
+	key->mode = READ_ONCE(inode->i_mode);
+	key->uid = inode->i_uid;
+	key->gid = inode->i_gid;
+	key->ctime_sec = ctime.tv_sec;
+	key->ctime_nsec = ctime.tv_nsec;
+	key->mtime_sec = mtime.tv_sec;
+	key->mtime_nsec = mtime.tv_nsec;
+}
+
+bool spawn_template_file_key_matches(struct file *file,
+				     const struct spawn_template_file_key *key)
+{
+	struct spawn_template_file_key cur;
+
+	spawn_template_fill_file_key(file, &cur);
+
+	return cur.dev == key->dev &&
+	       cur.ino == key->ino &&
+	       cur.size == key->size &&
+	       cur.mode == key->mode &&
+	       uid_eq(cur.uid, key->uid) &&
+	       gid_eq(cur.gid, key->gid) &&
+	       cur.ctime_sec == key->ctime_sec &&
+	       cur.ctime_nsec == key->ctime_nsec &&
+	       cur.mtime_sec == key->mtime_sec &&
+	       cur.mtime_nsec == key->mtime_nsec;
+}
+
 static int spawn_template_exit_status(int err)
 {
 	switch (err) {
@@ -58,6 +100,32 @@ static bool spawn_template_cred_matches(struct spawn_template *tmpl)
 	return current_cred() == tmpl->creator_cred;
 }
 
+static bool spawn_template_key_matches(struct spawn_template *tmpl)
+{
+	bool matches;
+
+	if (tmpl->filename) {
+		struct file *file __free(fput) = NULL;
+		struct file *tmp;
+
+		tmp = open_exec(tmpl->filename);
+		if (IS_ERR(tmp))
+			return false;
+		file = tmp;
+
+		matches = spawn_template_file_key_matches(file,
+							  &tmpl->exec_key);
+		matches = matches && spawn_template_file_exec_allowed(file);
+		exe_file_allow_write_access(file);
+		if (!matches)
+			return false;
+	}
+
+	return spawn_template_file_exec_allowed(tmpl->exec_file) &&
+	       spawn_template_file_key_matches(tmpl->exec_file,
+					       &tmpl->exec_key);
+}
+
 static int spawn_template_copy_signal_set(const struct spawn_template_action *action,
 					  sigset_t *mask)
 {
@@ -433,6 +501,7 @@ SYSCALL_DEFINE2(spawn_template_create,
 						 &tmpl->deny_write);
 	if (ret)
 		goto out_free_tmpl;
+	spawn_template_fill_file_key(tmpl->exec_file, &tmpl->exec_key);
 
 	if (args.flags & SPAWN_TEMPLATE_CREATE_CLOEXEC)
 		fd_flags |= O_CLOEXEC;
@@ -507,6 +576,11 @@ SYSCALL_DEFINE3(spawn_template_spawn, int, template_fd,
 	if (ret)
 		goto out_free_ctx;
 
+	if (!spawn_template_key_matches(ctx->tmpl)) {
+		ret = -ESTALE;
+		goto out_free_actions;
+	}
+
 	kargs = (struct kernel_clone_args) {
 		.flags		= CLONE_VM | CLONE_VFORK | CLONE_PIDFD,
 		.pidfd		= u64_to_user_ptr(ctx->args.pidfd),
@@ -517,6 +591,7 @@ SYSCALL_DEFINE3(spawn_template_spawn, int, template_fd,
 
 	ret = kernel_clone(&kargs);
 
+out_free_actions:
 	kfree(ctx->actions);
 out_free_ctx:
 	kfree(ctx);
diff --git a/include/linux/spawn_template.h b/include/linux/spawn_template.h
new file mode 100644
index 0000000000000..f14a7749fe55b
--- /dev/null
+++ b/include/linux/spawn_template.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_SPAWN_TEMPLATE_H
+#define _LINUX_SPAWN_TEMPLATE_H
+
+#include <linux/fs.h>
+
+struct spawn_template_file_key {
+	dev_t dev;
+	ino_t ino;
+	loff_t size;
+	umode_t mode;
+	kuid_t uid;
+	kgid_t gid;
+	u64 ctime_sec;
+	u64 ctime_nsec;
+	u64 mtime_sec;
+	u64 mtime_nsec;
+};
+
+void spawn_template_fill_file_key(struct file *file,
+				  struct spawn_template_file_key *key);
+bool spawn_template_file_key_matches(struct file *file,
+				     const struct spawn_template_file_key *key);
+
+#endif /* _LINUX_SPAWN_TEMPLATE_H */
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 06/13] exec: add spawn_template_spawn()
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Add spawn_template_spawn() to start a child from a template fd. The child
uses the template's pinned executable file, runs per-spawn fd, cwd, and
signal actions, closes non-stdio fds by default, and then executes through
the normal opened-file exec path.
Return a pidfd for the child so userspace can wait or signal it without
racy pid reuse. Keep fd inheritance opt-in with
SPAWN_TEMPLATE_SPAWN_INHERIT_FDS.
This patch consumes cached template state but does not add ELF metadata
caching; executable identity and ELF metadata caching are added separately.

Signed-off-by: Li Chen <me@linux.beauty>
---
 fs/spawn_template.c      | 346 +++++++++++++++++++++++++++++++++++++++
 include/linux/syscalls.h |   4 +
 2 files changed, 350 insertions(+)

diff --git a/fs/spawn_template.c b/fs/spawn_template.c
index 280a1038cc45e..8c3711929cffb 100644
--- a/fs/spawn_template.c
+++ b/fs/spawn_template.c
@@ -1,14 +1,24 @@
 // SPDX-License-Identifier: GPL-2.0-only
 #include <linux/anon_inodes.h>
+#include <linux/binfmts.h>
+#include <linux/close_range.h>
 #include <linux/cred.h>
 #include <linux/err.h>
 #include <linux/fcntl.h>
+#include <linux/fdtable.h>
 #include <linux/file.h>
 #include <linux/fs.h>
+#include <linux/fs_struct.h>
 #include <linux/kernel.h>
+#include <linux/namei.h>
+#include <linux/sched/signal.h>
+#include <linux/sched/task.h>
+#include <linux/signal.h>
 #include <linux/slab.h>
+#include <linux/string.h>
 #include <linux/syscalls.h>
 #include <linux/uaccess.h>
+#include <uapi/linux/openat2.h>
 #include <uapi/linux/spawn_template.h>
 
 #include "internal.h"
@@ -22,8 +32,262 @@ struct spawn_template {
 	bool deny_write;
 };
 
+struct spawn_template_spawn_context {
+	struct spawn_template *tmpl;
+	struct spawn_template_spawn_args args;
+	struct spawn_template_action *actions;
+};
+
 static const struct file_operations spawn_template_fops;
 
+static int spawn_template_exit_status(int err)
+{
+	switch (err) {
+	case -ENOENT:
+		return 127;
+	case -EACCES:
+	case -ENOEXEC:
+		return 126;
+	default:
+		return 1;
+	}
+}
+
+static bool spawn_template_cred_matches(struct spawn_template *tmpl)
+{
+	return current_cred() == tmpl->creator_cred;
+}
+
+static int spawn_template_copy_signal_set(const struct spawn_template_action *action,
+					  sigset_t *mask)
+{
+	struct spawn_template_sigset sigset;
+
+	if (!action->arg)
+		return -EINVAL;
+	if (copy_from_user(&sigset, u64_to_user_ptr(action->arg),
+			   sizeof(sigset)))
+		return -EFAULT;
+	if (sigset.sigsetsize != sizeof(sigset_t))
+		return -EINVAL;
+	if (copy_from_user(mask, u64_to_user_ptr(sigset.sigset), sizeof(*mask)))
+		return -EFAULT;
+	sigdelsetmask(mask, sigmask(SIGKILL) | sigmask(SIGSTOP));
+
+	return 0;
+}
+
+static int spawn_template_apply_open(const struct spawn_template_action *action)
+{
+	struct spawn_template_open open;
+	struct file *file __free(fput) = NULL;
+	struct file *tmp;
+	struct open_flags op;
+	int ret;
+
+	if (action->fd < AT_FDCWD || action->newfd < 0 || action->flags ||
+	    !action->arg)
+		return -EINVAL;
+
+	if (copy_from_user(&open, u64_to_user_ptr(action->arg), sizeof(open)))
+		return -EFAULT;
+
+	ret = build_open_flags(&open.how, &op);
+	if (ret)
+		return ret;
+
+	CLASS(filename_flags, name)(u64_to_user_ptr(open.path), op.lookup_flags);
+	tmp = do_file_open(action->fd, name, &op);
+	if (IS_ERR(tmp))
+		return PTR_ERR(tmp);
+	file = tmp;
+
+	return replace_fd(action->newfd, file, open.how.flags & O_CLOEXEC);
+}
+
+static int spawn_template_apply_sigmask(const struct spawn_template_action *action)
+{
+	sigset_t mask;
+	int ret;
+
+	if (action->fd || action->newfd || action->flags)
+		return -EINVAL;
+
+	ret = spawn_template_copy_signal_set(action, &mask);
+	if (ret)
+		return ret;
+
+	set_current_blocked(&mask);
+	return 0;
+}
+
+static int spawn_template_apply_sigdefault(const struct spawn_template_action *action)
+{
+	sigset_t mask;
+	struct k_sigaction sa = {};
+	int ret;
+	int sig;
+
+	if (action->fd || action->newfd || action->flags)
+		return -EINVAL;
+
+	ret = spawn_template_copy_signal_set(action, &mask);
+	if (ret)
+		return ret;
+
+	sa.sa.sa_handler = SIG_DFL;
+	sigemptyset(&sa.sa.sa_mask);
+
+	for (sig = 1; sig < _NSIG; sig++) {
+		if (!sigismember(&mask, sig))
+			continue;
+		ret = do_sigaction(sig, &sa, NULL);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int spawn_template_apply_action(const struct spawn_template_action *action)
+{
+	switch (action->type) {
+	case SPAWN_TEMPLATE_ACTION_CLOSE:
+		return close_fd(action->fd);
+	case SPAWN_TEMPLATE_ACTION_DUP2:
+		if (action->fd == action->newfd) {
+			if (action->flags)
+				return -EINVAL;
+			CLASS(fd, f)(action->fd);
+
+			if (fd_empty(f))
+				return -EBADF;
+			return 0;
+		}
+		return ksys_dup3(action->fd, action->newfd, action->flags);
+	case SPAWN_TEMPLATE_ACTION_FCHDIR: {
+		CLASS(fd, f)(action->fd);
+		int ret;
+
+		if (fd_empty(f))
+			return -EBADF;
+		if (!d_can_lookup(fd_file(f)->f_path.dentry))
+			return -ENOTDIR;
+
+		ret = file_permission(fd_file(f), MAY_EXEC | MAY_CHDIR);
+		if (!ret)
+			set_fs_pwd(current->fs, &fd_file(f)->f_path);
+		return ret;
+	}
+	case SPAWN_TEMPLATE_ACTION_OPEN:
+		return spawn_template_apply_open(action);
+	case SPAWN_TEMPLATE_ACTION_CLOSE_RANGE:
+		return do_close_range(action->fd, action->newfd, action->flags);
+	case SPAWN_TEMPLATE_ACTION_SIGMASK:
+		return spawn_template_apply_sigmask(action);
+	case SPAWN_TEMPLATE_ACTION_SIGDEFAULT:
+		return spawn_template_apply_sigdefault(action);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int spawn_template_copy_actions(struct spawn_template_action **out_actions,
+				       u64 count, u64 uaddr)
+{
+	struct spawn_template_action __user *uactions;
+	struct spawn_template_action *actions __free(kfree) = NULL;
+	struct spawn_template_action *tmp;
+	u64 i;
+
+	*out_actions = NULL;
+	if (!count)
+		return 0;
+	if (count > SPAWN_TEMPLATE_MAX_ACTIONS)
+		return -E2BIG;
+	if (!uaddr)
+		return -EINVAL;
+
+	uactions = u64_to_user_ptr(uaddr);
+	tmp = memdup_array_user(uactions, count, sizeof(*actions));
+	if (IS_ERR(tmp))
+		return PTR_ERR(tmp);
+	actions = tmp;
+
+	for (i = 0; i < count; i++) {
+		switch (actions[i].type) {
+		case SPAWN_TEMPLATE_ACTION_CLOSE:
+			if (actions[i].fd < 0 || actions[i].flags ||
+			    actions[i].newfd || actions[i].arg)
+				return -EINVAL;
+			break;
+		case SPAWN_TEMPLATE_ACTION_DUP2:
+			if (actions[i].fd < 0 || actions[i].newfd < 0 ||
+			    (actions[i].flags & ~O_CLOEXEC) || actions[i].arg)
+				return -EINVAL;
+			break;
+		case SPAWN_TEMPLATE_ACTION_FCHDIR:
+			if (actions[i].fd < 0 || actions[i].flags ||
+			    actions[i].newfd || actions[i].arg)
+				return -EINVAL;
+			break;
+		case SPAWN_TEMPLATE_ACTION_OPEN:
+			if (actions[i].fd < AT_FDCWD || actions[i].newfd < 0 ||
+			    actions[i].flags || !actions[i].arg)
+				return -EINVAL;
+			break;
+		case SPAWN_TEMPLATE_ACTION_CLOSE_RANGE:
+			if (actions[i].fd < 0 || actions[i].newfd < 0 ||
+			    actions[i].fd > actions[i].newfd ||
+			    (actions[i].flags &
+			     ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC)) ||
+			    actions[i].arg)
+				return -EINVAL;
+			break;
+		case SPAWN_TEMPLATE_ACTION_SIGMASK:
+		case SPAWN_TEMPLATE_ACTION_SIGDEFAULT:
+			if (actions[i].fd || actions[i].newfd ||
+			    actions[i].flags || !actions[i].arg)
+				return -EINVAL;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	*out_actions = no_free_ptr(actions);
+	return 0;
+}
+
+static int spawn_template_child(void *data)
+{
+	struct spawn_template_spawn_context *ctx = data;
+	struct spawn_template *tmpl = ctx->tmpl;
+	int ret;
+	u64 i;
+
+	for (i = 0; i < ctx->args.actions_len; i++) {
+		ret = spawn_template_apply_action(&ctx->actions[i]);
+		if (ret < 0)
+			goto out_exec_error;
+	}
+
+	if (!(ctx->args.flags & SPAWN_TEMPLATE_SPAWN_INHERIT_FDS)) {
+		ret = do_close_range(3, ~0U, 0);
+		if (ret < 0)
+			goto out_exec_error;
+	}
+
+	ret = kernel_execveat_file(tmpl->exec_file, "",
+				   u64_to_user_ptr(ctx->args.argv),
+				   u64_to_user_ptr(ctx->args.envp),
+				   AT_EMPTY_PATH);
+out_exec_error:
+	if (ret < 0)
+		do_exit(spawn_template_exit_status(ret));
+	return 0;
+}
+
 static bool spawn_template_file_exec_allowed(struct file *file)
 {
 	if (!S_ISREG(file_inode(file)->i_mode))
@@ -53,6 +317,18 @@ static const struct file_operations spawn_template_fops = {
 	.llseek		= noop_llseek,
 };
 
+static struct file *spawn_template_file_from_fd(int fd)
+{
+	CLASS(fd, f)(fd);
+
+	if (fd_empty(f))
+		return ERR_PTR(-EBADF);
+	if (fd_file(f)->f_op != &spawn_template_fops)
+		return ERR_PTR(-EINVAL);
+
+	return get_file(fd_file(f));
+}
+
 static int spawn_template_open_execfd(int execfd, struct file **file,
 				      bool *deny_write)
 {
@@ -178,3 +454,73 @@ SYSCALL_DEFINE2(spawn_template_create,
 	kfree(tmpl);
 	return ret;
 }
+
+SYSCALL_DEFINE3(spawn_template_spawn, int, template_fd,
+		struct spawn_template_spawn_args __user *, uargs,
+		size_t, usize)
+{
+	struct spawn_template_spawn_context *ctx;
+	struct kernel_clone_args kargs;
+	struct file *template_file;
+	int ret;
+
+	BUILD_BUG_ON(sizeof(struct spawn_template_spawn_args) !=
+		     SPAWN_TEMPLATE_SPAWN_ARGS_SIZE_VER0);
+
+	if (usize < SPAWN_TEMPLATE_SPAWN_ARGS_SIZE_VER0)
+		return -EINVAL;
+	if (usize > PAGE_SIZE)
+		return -E2BIG;
+
+	template_file = spawn_template_file_from_fd(template_fd);
+	if (IS_ERR(template_file))
+		return PTR_ERR(template_file);
+
+	if (!spawn_template_cred_matches(template_file->private_data)) {
+		ret = -EACCES;
+		goto out_put_template;
+	}
+
+	ctx = kzalloc_obj(*ctx, GFP_KERNEL);
+	if (!ctx) {
+		ret = -ENOMEM;
+		goto out_put_template;
+	}
+
+	ctx->tmpl = template_file->private_data;
+
+	ret = copy_struct_from_user(&ctx->args, sizeof(ctx->args), uargs,
+				    usize);
+	if (ret)
+		goto out_free_ctx;
+
+	if ((ctx->args.flags & ~SPAWN_TEMPLATE_SPAWN_INHERIT_FDS) ||
+	    !ctx->args.pidfd || ctx->args.reserved[0] ||
+	    ctx->args.reserved[1] || ctx->args.reserved[2] ||
+	    ctx->args.reserved[3]) {
+		ret = -EINVAL;
+		goto out_free_ctx;
+	}
+
+	ret = spawn_template_copy_actions(&ctx->actions, ctx->args.actions_len,
+					  ctx->args.actions);
+	if (ret)
+		goto out_free_ctx;
+
+	kargs = (struct kernel_clone_args) {
+		.flags		= CLONE_VM | CLONE_VFORK | CLONE_PIDFD,
+		.pidfd		= u64_to_user_ptr(ctx->args.pidfd),
+		.exit_signal	= SIGCHLD,
+		.fn		= spawn_template_child,
+		.fn_arg		= ctx,
+	};
+
+	ret = kernel_clone(&kargs);
+
+	kfree(ctx->actions);
+out_free_ctx:
+	kfree(ctx);
+out_put_template:
+	fput(template_file);
+	return ret;
+}
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 4b41950488bd6..df7368edf6778 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -68,6 +68,7 @@ union bpf_attr;
 struct io_uring_params;
 struct clone_args;
 struct spawn_template_create_args;
+struct spawn_template_spawn_args;
 struct open_how;
 struct mount_attr;
 struct landlock_ruleset_attr;
@@ -824,6 +825,9 @@ asmlinkage long sys_clone(unsigned long, unsigned long, int __user *,
 asmlinkage long sys_clone3(struct clone_args __user *uargs, size_t size);
 asmlinkage long sys_spawn_template_create(struct spawn_template_create_args __user *uargs,
 					  size_t size);
+asmlinkage long sys_spawn_template_spawn(int template_fd,
+					 struct spawn_template_spawn_args __user *uargs,
+					 size_t size);
 
 asmlinkage long sys_execve(const char __user *filename,
 		const char __user *const __user *argv,
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 05/13] exec: add spawn template file descriptors
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Add spawn_template_create() and back each template with an anon-inode fd.
Creation records the per-template state that later spawns reuse: the opened
executable file, optional absolute path, creator credential, and deny-write
state. Keep write access denied until the template fd is released so cached
state cannot race with writers.
This patch only creates and releases template fds.
Spawning and ELF metadata caching are added separately.

Signed-off-by: Li Chen <me@linux.beauty>
---
 MAINTAINERS                            |   1 +
 arch/x86/entry/syscalls/syscall_64.tbl |   1 -
 fs/Makefile                            |   2 +-
 fs/spawn_template.c                    | 180 +++++++++++++++++++++++++
 include/linux/syscalls.h               |   3 +
 5 files changed, 185 insertions(+), 2 deletions(-)
 create mode 100644 fs/spawn_template.c

diff --git a/MAINTAINERS b/MAINTAINERS
index d7b1191e33ca0..d5441812825c3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9732,6 +9732,7 @@ F:	Documentation/userspace-api/ELF.rst
 F:	fs/*binfmt_*.c
 F:	fs/Kconfig.binfmt
 F:	fs/exec.c
+F:	fs/spawn_template.c
 F:	fs/tests/binfmt_*_kunit.c
 F:	fs/tests/exec_kunit.c
 F:	include/linux/binfmts.h
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 524155d655da1..d6c1667e8f3b8 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -396,7 +396,6 @@
 469	common	file_setattr		sys_file_setattr
 470	common	listns			sys_listns
 471	common	rseq_slice_yield	sys_rseq_slice_yield
-
 #
 # Due to a historical design error, certain syscalls are numbered differently
 # in x32 as compared to native x86_64.  These syscalls have numbers 512-547.
diff --git a/fs/Makefile b/fs/Makefile
index ae1b07f9c6a0c..796eb4ae143e5 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -8,7 +8,7 @@
 
 
 obj-y :=	open.o read_write.o file_table.o super.o \
-		char_dev.o stat.o exec.o pipe.o namei.o fcntl.o \
+		char_dev.o stat.o exec.o spawn_template.o pipe.o namei.o fcntl.o \
 		ioctl.o readdir.o select.o dcache.o inode.o \
 		attr.o bad_inode.o file.o filesystems.o namespace.o \
 		seq_file.o xattr.o libfs.o fs-writeback.o \
diff --git a/fs/spawn_template.c b/fs/spawn_template.c
new file mode 100644
index 0000000000000..280a1038cc45e
--- /dev/null
+++ b/fs/spawn_template.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/anon_inodes.h>
+#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/spawn_template.h>
+
+#include "internal.h"
+
+#define SPAWN_TEMPLATE_MAX_ACTIONS	256
+
+struct spawn_template {
+	struct file *exec_file;
+	const struct cred *creator_cred;
+	char *filename;
+	bool deny_write;
+};
+
+static const struct file_operations spawn_template_fops;
+
+static bool spawn_template_file_exec_allowed(struct file *file)
+{
+	if (!S_ISREG(file_inode(file)->i_mode))
+		return false;
+	if (path_noexec(&file->f_path))
+		return false;
+	if (file_permission(file, MAY_EXEC))
+		return false;
+	return can_mmap_file(file);
+}
+
+static int spawn_template_release(struct inode *inode, struct file *file)
+{
+	struct spawn_template *tmpl = file->private_data;
+
+	if (tmpl->deny_write)
+		exe_file_allow_write_access(tmpl->exec_file);
+	fput(tmpl->exec_file);
+	put_cred(tmpl->creator_cred);
+	kfree(tmpl->filename);
+	kfree(tmpl);
+	return 0;
+}
+
+static const struct file_operations spawn_template_fops = {
+	.release	= spawn_template_release,
+	.llseek		= noop_llseek,
+};
+
+static int spawn_template_open_execfd(int execfd, struct file **file,
+				      bool *deny_write)
+{
+	int ret;
+
+	if (execfd < 0)
+		return -EINVAL;
+
+	CLASS(fd, f)(execfd);
+	if (fd_empty(f))
+		return -EBADF;
+
+	if (!spawn_template_file_exec_allowed(fd_file(f)))
+		return -EACCES;
+
+	ret = exe_file_deny_write_access(fd_file(f));
+	if (ret)
+		return ret;
+
+	*file = get_file(fd_file(f));
+	*deny_write = true;
+	return 0;
+}
+
+static int spawn_template_open_filename(u64 filename, struct file **file,
+					char **path,
+					bool *deny_write)
+{
+	char *kfilename __free(kfree) = NULL;
+	struct file *exec __free(fput) = NULL;
+	struct file *tmp_file;
+	char *tmp;
+
+	if (!filename)
+		return -EINVAL;
+
+	tmp = strndup_user(u64_to_user_ptr(filename), PATH_MAX);
+	if (IS_ERR(tmp))
+		return PTR_ERR(tmp);
+	kfilename = tmp;
+
+	tmp_file = open_exec(kfilename);
+	if (IS_ERR(tmp_file))
+		return PTR_ERR(tmp_file);
+	exec = tmp_file;
+	if (!spawn_template_file_exec_allowed(exec)) {
+		exe_file_allow_write_access(exec);
+		return -EACCES;
+	}
+
+	*file = no_free_ptr(exec);
+	*path = no_free_ptr(kfilename);
+	*deny_write = true;
+	return 0;
+}
+
+SYSCALL_DEFINE2(spawn_template_create,
+		struct spawn_template_create_args __user *, uargs,
+		size_t, usize)
+{
+	struct spawn_template_create_args args;
+	struct spawn_template *tmpl;
+	int fd_flags = 0;
+	int ret;
+
+	BUILD_BUG_ON(sizeof(struct spawn_template_create_args) !=
+		     SPAWN_TEMPLATE_CREATE_ARGS_SIZE_VER0);
+
+	if (usize < SPAWN_TEMPLATE_CREATE_ARGS_SIZE_VER0)
+		return -EINVAL;
+	if (usize > PAGE_SIZE)
+		return -E2BIG;
+
+	ret = copy_struct_from_user(&args, sizeof(args), uargs, usize);
+	if (ret)
+		return ret;
+
+	if (args.flags & ~SPAWN_TEMPLATE_CREATE_CLOEXEC)
+		return -EINVAL;
+	if (args.exec_flags || args.reserved[0] || args.reserved[1] ||
+	    args.reserved[2] || args.reserved[3])
+		return -EINVAL;
+	if (args.actions || args.actions_len)
+		return -EINVAL;
+	if ((args.execfd < 0 && !args.filename) ||
+	    (args.execfd >= 0 && args.filename))
+		return -EINVAL;
+
+	tmpl = kzalloc_obj(*tmpl, GFP_KERNEL);
+	if (!tmpl)
+		return -ENOMEM;
+	tmpl->creator_cred = get_current_cred();
+
+	if (args.filename)
+		ret = spawn_template_open_filename(args.filename,
+						   &tmpl->exec_file,
+						   &tmpl->filename,
+						   &tmpl->deny_write);
+	else
+		ret = spawn_template_open_execfd(args.execfd,
+						 &tmpl->exec_file,
+						 &tmpl->deny_write);
+	if (ret)
+		goto out_free_tmpl;
+
+	if (args.flags & SPAWN_TEMPLATE_CREATE_CLOEXEC)
+		fd_flags |= O_CLOEXEC;
+
+	ret = anon_inode_getfd("spawn_template", &spawn_template_fops, tmpl,
+			       fd_flags);
+	if (ret < 0)
+		goto out_put_exec;
+
+	return ret;
+
+out_put_exec:
+	if (tmpl->deny_write)
+		exe_file_allow_write_access(tmpl->exec_file);
+	fput(tmpl->exec_file);
+out_free_tmpl:
+	put_cred(tmpl->creator_cred);
+	kfree(tmpl->filename);
+	kfree(tmpl);
+	return ret;
+}
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index f3dfc3269188a..4b41950488bd6 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -67,6 +67,7 @@ struct rseq;
 union bpf_attr;
 struct io_uring_params;
 struct clone_args;
+struct spawn_template_create_args;
 struct open_how;
 struct mount_attr;
 struct landlock_ruleset_attr;
@@ -821,6 +822,8 @@ asmlinkage long sys_clone(unsigned long, unsigned long, int __user *,
 #endif
 
 asmlinkage long sys_clone3(struct clone_args __user *uargs, size_t size);
+asmlinkage long sys_spawn_template_create(struct spawn_template_create_args __user *uargs,
+					  size_t size);
 
 asmlinkage long sys_execve(const char __user *filename,
 		const char __user *const __user *argv,
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 04/13] exec: add spawn template UAPI definitions
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Add the userspace ABI structures and flags for creating a spawn
template and spawning a process from it. The ABI carries argv, envp,
and per-spawn fd actions while leaving policy decisions in userspace.

Signed-off-by: Li Chen <me@linux.beauty>
---
 MAINTAINERS                         |  1 +
 include/uapi/linux/spawn_template.h | 62 +++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+)
 create mode 100644 include/uapi/linux/spawn_template.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 3dd58a16f06a9..d7b1191e33ca0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9739,6 +9739,7 @@ F:	include/linux/elf.h
 F:	include/uapi/linux/auxvec.h
 F:	include/uapi/linux/binfmts.h
 F:	include/uapi/linux/elf.h
+F:	include/uapi/linux/spawn_template.h
 F:	kernel/fork.c
 F:	mm/vma_exec.c
 F:	tools/testing/selftests/exec/
diff --git a/include/uapi/linux/spawn_template.h b/include/uapi/linux/spawn_template.h
new file mode 100644
index 0000000000000..84f026fdf9090
--- /dev/null
+++ b/include/uapi/linux/spawn_template.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_SPAWN_TEMPLATE_H
+#define _UAPI_LINUX_SPAWN_TEMPLATE_H
+
+#include <linux/openat2.h>
+#include <linux/types.h>
+
+#define SPAWN_TEMPLATE_CREATE_CLOEXEC		(1ULL << 0)
+#define SPAWN_TEMPLATE_SPAWN_INHERIT_FDS	(1ULL << 0)
+
+enum spawn_template_action_type {
+	SPAWN_TEMPLATE_ACTION_CLOSE = 0,
+	SPAWN_TEMPLATE_ACTION_DUP2 = 1,
+	SPAWN_TEMPLATE_ACTION_FCHDIR = 2,
+	SPAWN_TEMPLATE_ACTION_OPEN = 3,
+	SPAWN_TEMPLATE_ACTION_CLOSE_RANGE = 4,
+	SPAWN_TEMPLATE_ACTION_SIGMASK = 5,
+	SPAWN_TEMPLATE_ACTION_SIGDEFAULT = 6,
+};
+
+struct spawn_template_action {
+	__u32 type;
+	__u32 flags;
+	__s32 fd;
+	__s32 newfd;
+	__aligned_u64 arg;
+};
+
+struct spawn_template_open {
+	__aligned_u64 path;
+	struct open_how how;
+};
+
+struct spawn_template_sigset {
+	__aligned_u64 sigset;
+	__u64 sigsetsize;
+};
+
+struct spawn_template_create_args {
+	__aligned_u64 flags;
+	__s32 execfd;
+	__u32 exec_flags;
+	__aligned_u64 filename;
+	__aligned_u64 actions;
+	__aligned_u64 actions_len;
+	__aligned_u64 reserved[4];
+};
+
+struct spawn_template_spawn_args {
+	__aligned_u64 flags;
+	__aligned_u64 pidfd;
+	__aligned_u64 argv;
+	__aligned_u64 envp;
+	__aligned_u64 actions;
+	__aligned_u64 actions_len;
+	__aligned_u64 reserved[4];
+};
+
+#define SPAWN_TEMPLATE_CREATE_ARGS_SIZE_VER0	72
+#define SPAWN_TEMPLATE_SPAWN_ARGS_SIZE_VER0	80
+
+#endif /* _UAPI_LINUX_SPAWN_TEMPLATE_H */
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 03/13] file: expose helpers for in-kernel fd actions
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Split do_close_range() from the close_range syscall wrapper and make
ksys_dup3() available to in-kernel callers. Later spawn-template fd
actions use these helpers instead of duplicating close and dup logic.

Signed-off-by: Li Chen <me@linux.beauty>
---
 fs/file.c               | 11 ++++++++---
 include/linux/fdtable.h |  2 ++
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/fs/file.c b/fs/file.c
index e5c75b22e0c7c..a9f4b4e2dcd45 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -815,8 +815,7 @@ static inline void __range_close(struct files_struct *files, unsigned int fd,
  * from @fd up to and including @max_fd are closed.
  * Currently, errors to close a given file descriptor are ignored.
  */
-SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd,
-		unsigned int, flags)
+int do_close_range(unsigned int fd, unsigned int max_fd, unsigned int flags)
 {
 	struct task_struct *me = current;
 	struct files_struct *cur_fds = me->files, *fds = NULL;
@@ -867,6 +866,12 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd,
 	return 0;
 }
 
+SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd,
+		unsigned int, flags)
+{
+	return do_close_range(fd, max_fd, flags);
+}
+
 /**
  * file_close_fd - return file associated with fd
  * @fd: file descriptor to retrieve file for
@@ -1421,7 +1426,7 @@ int receive_fd_replace(int new_fd, struct file *file, unsigned int o_flags)
 	return new_fd;
 }
 
-static int ksys_dup3(unsigned int oldfd, unsigned int newfd, int flags)
+int ksys_dup3(unsigned int oldfd, unsigned int newfd, int flags)
 {
 	int err = -EBADF;
 	struct file *file;
diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h
index c45306a9f0072..7f852fcc082a4 100644
--- a/include/linux/fdtable.h
+++ b/include/linux/fdtable.h
@@ -112,6 +112,8 @@ int iterate_fd(struct files_struct *, unsigned,
 
 extern int close_fd(unsigned int fd);
 extern struct file *file_close_fd(unsigned int fd);
+int do_close_range(unsigned int fd, unsigned int max_fd, unsigned int flags);
+int ksys_dup3(unsigned int oldfd, unsigned int newfd, int flags);
 
 extern struct kmem_cache *files_cachep;
 
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 02/13] exec: add an internal helper for opened executables
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Split alloc_bprm_file() from alloc_bprm() so internal callers can build
a linux_binprm from an executable file that they already opened.
Add kernel_execveat_file() for in-kernel users that need to execute an
opened file while still using the normal execve credential, LSM, and
binary-format path.

Signed-off-by: Li Chen <me@linux.beauty>
---
 fs/exec.c               | 78 +++++++++++++++++++++++++++++++++++------
 include/linux/binfmts.h |  4 +++
 2 files changed, 71 insertions(+), 11 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index 53f7b18d2b1ea..5b91a9b208a77 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1392,16 +1392,13 @@ static void free_bprm(struct linux_binprm *bprm)
 	kfree(bprm);
 }
 
-static struct linux_binprm *alloc_bprm(int fd, struct filename *filename, int flags)
+static struct linux_binprm *alloc_bprm_file(struct file *file,
+					    struct filename *filename,
+					    int fd, int flags)
 {
 	struct linux_binprm *bprm;
-	struct file *file;
 	int retval = -ENOMEM;
 
-	file = do_open_execat(fd, filename, flags);
-	if (IS_ERR(file))
-		return ERR_CAST(file);
-
 	bprm = kzalloc_obj(*bprm);
 	if (!bprm) {
 		do_close_execat(file);
@@ -1463,6 +1460,17 @@ static struct linux_binprm *alloc_bprm(int fd, struct filename *filename, int fl
 	return ERR_PTR(retval);
 }
 
+static struct linux_binprm *alloc_bprm(int fd, struct filename *filename, int flags)
+{
+	struct file *file;
+
+	file = do_open_execat(fd, filename, flags);
+	if (IS_ERR(file))
+		return ERR_CAST(file);
+
+	return alloc_bprm_file(file, filename, fd, flags);
+}
+
 DEFINE_CLASS(bprm, struct linux_binprm *, if (!IS_ERR(_T)) free_bprm(_T),
 	alloc_bprm(fd, name, flags), int fd, struct filename *name, int flags)
 
@@ -1901,6 +1909,59 @@ int kernel_execve(const char *kernel_filename,
 	return bprm_execve(bprm);
 }
 
+static inline struct user_arg_ptr native_arg(const char __user *const __user *p)
+{
+	return (struct user_arg_ptr){.ptr.native = p};
+}
+
+static int do_execveat_file_common(struct file *file, struct filename *filename,
+				   struct user_arg_ptr argv,
+				   struct user_arg_ptr envp, int flags)
+{
+	struct linux_binprm *bprm;
+	struct file *exec_file;
+	int retval;
+
+	if (flags & ~AT_EMPTY_PATH)
+		return -EINVAL;
+
+	if ((current->flags & PF_NPROC_EXCEEDED) &&
+	    is_rlimit_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)))
+		return -EAGAIN;
+
+	current->flags &= ~PF_NPROC_EXCEEDED;
+
+	retval = exe_file_deny_write_access(file);
+	if (retval)
+		return retval;
+	exec_file = get_file(file);
+
+	bprm = alloc_bprm_file(exec_file, filename, AT_FDCWD, flags);
+	if (IS_ERR(bprm))
+		return PTR_ERR(bprm);
+
+	retval = do_execveat_common_bprm(bprm, argv, envp);
+	free_bprm(bprm);
+	return retval;
+}
+
+int kernel_execveat_file(struct file *file, const char *filename,
+			 const void __user *argv,
+			 const void __user *envp,
+			 int flags)
+{
+	const char __user *const __user *user_argv;
+	const char __user *const __user *user_envp;
+
+	CLASS(filename_kernel, name)(filename);
+
+	user_argv = (const char __user *const __user *)argv;
+	user_envp = (const char __user *const __user *)envp;
+
+	return do_execveat_file_common(file, name, native_arg(user_argv),
+				       native_arg(user_envp), flags);
+}
+
 void set_binfmt(struct linux_binfmt *new)
 {
 	struct mm_struct *mm = current->mm;
@@ -1925,11 +1986,6 @@ void set_dumpable(struct mm_struct *mm, int value)
 	__mm_flags_set_mask_dumpable(mm, value);
 }
 
-static inline struct user_arg_ptr native_arg(const char __user *const __user *p)
-{
-	return (struct user_arg_ptr){.ptr.native = p};
-}
-
 SYSCALL_DEFINE3(execve,
 		const char __user *, filename,
 		const char __user *const __user *, argv,
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 65abd5ab8836c..c0715678c9a06 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -141,6 +141,10 @@ extern int transfer_args_to_stack(struct linux_binprm *bprm,
 				  unsigned long *sp_location);
 extern int bprm_change_interp(const char *interp, struct linux_binprm *bprm);
 int copy_string_kernel(const char *arg, struct linux_binprm *bprm);
+int kernel_execveat_file(struct file *file, const char *filename,
+			 const void __user *argv,
+			 const void __user *envp,
+			 int flags);
 extern void set_binfmt(struct linux_binfmt *new);
 extern ssize_t read_code(struct file *, unsigned long, loff_t, size_t);
 
-- 
2.52.0


^ permalink raw reply related

* [RFC PATCH v1 01/13] exec: factor argument setup out of do_execveat_common()
From: Li Chen @ 2026-05-28  9:52 UTC (permalink / raw)
  To: Christian Brauner, Kees Cook, Alexander Viro
  Cc: linux-fsdevel, linux-api, linux-kernel, linux-mm, linux-arch,
	linux-doc, linux-kselftest, x86, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Li Chen
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>

Move the common userspace argv and envp counting and stack setup code
into do_execveat_common_bprm(). Keep do_execveat_common() responsible
for the existing RLIMIT_NPROC check, bprm allocation, and error path.
This is a mechanical refactor for later opened-file exec users. It
does not change execve or execveat behavior.

Signed-off-by: Li Chen <me@linux.beauty>
---
 fs/exec.c | 53 +++++++++++++++++++++++++++++++----------------------
 1 file changed, 31 insertions(+), 22 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index 2889b7cf808d7..53f7b18d2b1ea 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1775,31 +1775,12 @@ static int bprm_execve(struct linux_binprm *bprm)
 	return retval;
 }
 
-static int do_execveat_common(int fd, struct filename *filename,
-			      struct user_arg_ptr argv,
-			      struct user_arg_ptr envp,
-			      int flags)
+static int do_execveat_common_bprm(struct linux_binprm *bprm,
+				   struct user_arg_ptr argv,
+				   struct user_arg_ptr envp)
 {
 	int retval;
 
-	/*
-	 * We move the actual failure in case of RLIMIT_NPROC excess from
-	 * set*uid() to execve() because too many poorly written programs
-	 * don't check setuid() return code.  Here we additionally recheck
-	 * whether NPROC limit is still exceeded.
-	 */
-	if ((current->flags & PF_NPROC_EXCEEDED) &&
-	    is_rlimit_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)))
-		return -EAGAIN;
-
-	/* We're below the limit (still or again), so we don't want to make
-	 * further execve() calls fail. */
-	current->flags &= ~PF_NPROC_EXCEEDED;
-
-	CLASS(bprm, bprm)(fd, filename, flags);
-	if (IS_ERR(bprm))
-		return PTR_ERR(bprm);
-
 	retval = count(argv, MAX_ARG_STRINGS);
 	if (retval < 0)
 		return retval;
@@ -1846,6 +1827,34 @@ static int do_execveat_common(int fd, struct filename *filename,
 	return bprm_execve(bprm);
 }
 
+static int do_execveat_common(int fd, struct filename *filename,
+			      struct user_arg_ptr argv,
+			      struct user_arg_ptr envp,
+			      int flags)
+{
+	/*
+	 * We move the actual failure in case of RLIMIT_NPROC excess from
+	 * set*uid() to execve() because too many poorly written programs
+	 * don't check setuid() return code.  Here we additionally recheck
+	 * whether NPROC limit is still exceeded.
+	 */
+	if ((current->flags & PF_NPROC_EXCEEDED) &&
+	    is_rlimit_overlimit(current_ucounts(), UCOUNT_RLIMIT_NPROC, rlimit(RLIMIT_NPROC)))
+		return -EAGAIN;
+
+	/*
+	 * We're below the limit (still or again), so we don't want to make
+	 * further execve() calls fail.
+	 */
+	current->flags &= ~PF_NPROC_EXCEEDED;
+
+	CLASS(bprm, bprm)(fd, filename, flags);
+	if (IS_ERR(bprm))
+		return PTR_ERR(bprm);
+
+	return do_execveat_common_bprm(bprm, argv, envp);
+}
+
 int kernel_execve(const char *kernel_filename,
 		  const char *const *argv, const char *const *envp)
 {
-- 
2.52.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox