diff options
Diffstat (limited to 'tools')
276 files changed, 12086 insertions, 1325 deletions
diff --git a/tools/arch/s390/include/uapi/asm/kvm.h b/tools/arch/s390/include/uapi/asm/kvm.h index 60345dd2cba2..4192769b5ce0 100644 --- a/tools/arch/s390/include/uapi/asm/kvm.h +++ b/tools/arch/s390/include/uapi/asm/kvm.h @@ -444,6 +444,7 @@ struct kvm_s390_vm_cpu_machine { #define KVM_S390_VM_CPU_FEAT_PFMFI 11 #define KVM_S390_VM_CPU_FEAT_SIGPIF 12 #define KVM_S390_VM_CPU_FEAT_KSS 13 +#define KVM_S390_VM_CPU_FEAT_ASTFLEIE2 14 struct kvm_s390_vm_cpu_feat { __u64 feat[16]; }; diff --git a/tools/arch/x86/include/asm/cpufeatures.h b/tools/arch/x86/include/asm/cpufeatures.h index 86d17b195e79..55224157740b 100644 --- a/tools/arch/x86/include/asm/cpufeatures.h +++ b/tools/arch/x86/include/asm/cpufeatures.h @@ -225,7 +225,7 @@ #define X86_FEATURE_EPT_AD ( 8*32+17) /* "ept_ad" Intel Extended Page Table access-dirty bit */ #define X86_FEATURE_VMCALL ( 8*32+18) /* Hypervisor supports the VMCALL instruction */ #define X86_FEATURE_VMW_VMMCALL ( 8*32+19) /* VMware prefers VMMCALL hypercall instruction */ -#define X86_FEATURE_PVUNLOCK ( 8*32+20) /* PV unlock function */ +// free: was #define X86_FEATURE_PVUNLOCK ( 8*32+20) /* PV unlock function */ #define X86_FEATURE_VCPUPREEMPT ( 8*32+21) /* PV vcpu_is_preempted function */ #define X86_FEATURE_TDX_GUEST ( 8*32+22) /* "tdx_guest" Intel Trust Domain Extensions Guest */ @@ -379,6 +379,7 @@ #define X86_FEATURE_AVIC (15*32+13) /* "avic" Virtual Interrupt Controller */ #define X86_FEATURE_V_VMSAVE_VMLOAD (15*32+15) /* "v_vmsave_vmload" Virtual VMSAVE VMLOAD */ #define X86_FEATURE_VGIF (15*32+16) /* "vgif" Virtual GIF */ +#define X86_FEATURE_GMET (15*32+17) /* Guest Mode Execution Trap */ #define X86_FEATURE_X2AVIC (15*32+18) /* "x2avic" Virtual x2apic */ #define X86_FEATURE_V_SPEC_CTRL (15*32+20) /* "v_spec_ctrl" Virtual SPEC_CTRL */ #define X86_FEATURE_VNMI (15*32+25) /* "vnmi" Virtual NMI */ @@ -473,6 +474,7 @@ #define X86_FEATURE_GP_ON_USER_CPUID (20*32+17) /* User CPUID faulting */ #define X86_FEATURE_PREFETCHI (20*32+20) /* Prefetch Data/Instruction to Cache Level */ +#define X86_FEATURE_AVX512_BMM (20*32+23) /* AVX512 Bit Matrix Multiply instructions */ #define X86_FEATURE_ERAPS (20*32+24) /* Enhanced Return Address Predictor Security */ #define X86_FEATURE_SBPB (20*32+27) /* Selective Branch Prediction Barrier */ #define X86_FEATURE_IBPB_BRTYPE (20*32+28) /* MSR_PRED_CMD[IBPB] flushes all branch type predictions */ @@ -572,4 +574,5 @@ #define X86_BUG_ITS_NATIVE_ONLY X86_BUG( 1*32+ 8) /* "its_native_only" CPU is affected by ITS, VMX is not affected */ #define X86_BUG_TSA X86_BUG( 1*32+ 9) /* "tsa" CPU is affected by Transient Scheduler Attacks */ #define X86_BUG_VMSCAPE X86_BUG( 1*32+10) /* "vmscape" CPU is affected by VMSCAPE attacks from guests */ +#define X86_BUG_SEAMRET_INVD_VMCS X86_BUG( 1*32+11) /* "seamret_invd_vmcs" SEAMRET from P-SEAMLDR clears the current VMCS */ #endif /* _ASM_X86_CPUFEATURES_H */ diff --git a/tools/arch/x86/include/asm/msr-index.h b/tools/arch/x86/include/asm/msr-index.h index eff29645719b..18c4be75e927 100644 --- a/tools/arch/x86/include/asm/msr-index.h +++ b/tools/arch/x86/include/asm/msr-index.h @@ -674,6 +674,9 @@ #define MSR_AMD64_DC_CFG 0xc0011022 #define MSR_AMD64_TW_CFG 0xc0011023 +#define MSR_AMD64_FP_CFG 0xc0011028 +#define MSR_AMD64_FP_CFG_ZEN1_DENORM_FIX_BIT 9 + #define MSR_AMD64_DE_CFG 0xc0011029 #define MSR_AMD64_DE_CFG_LFENCE_SERIALIZE_BIT 1 #define MSR_AMD64_DE_CFG_LFENCE_SERIALIZE BIT_ULL(MSR_AMD64_DE_CFG_LFENCE_SERIALIZE_BIT) @@ -698,6 +701,8 @@ #define MSR_AMD64_IBSBRTARGET 0xc001103b #define MSR_AMD64_ICIBSEXTDCTL 0xc001103c #define MSR_AMD64_IBSOPDATA4 0xc001103d +#define MSR_AMD64_IBSOPCTL2 0xc001103e +#define MSR_AMD64_IBSFETCHCTL2 0xc001103f #define MSR_AMD64_IBS_REG_COUNT_MAX 8 /* includes MSR_AMD64_IBSBRTARGET */ #define MSR_AMD64_SVM_AVIC_DOORBELL 0xc001011b #define MSR_AMD64_VM_PAGE_FLUSH 0xc001011e @@ -765,12 +770,14 @@ #define MSR_AMD_CPPC_CAP2 0xc00102b2 #define MSR_AMD_CPPC_REQ 0xc00102b3 #define MSR_AMD_CPPC_STATUS 0xc00102b4 +#define MSR_AMD_CPPC_REQ2 0xc00102b5 /* Masks for use with MSR_AMD_CPPC_CAP1 */ #define AMD_CPPC_LOWEST_PERF_MASK GENMASK(7, 0) #define AMD_CPPC_LOWNONLIN_PERF_MASK GENMASK(15, 8) #define AMD_CPPC_NOMINAL_PERF_MASK GENMASK(23, 16) #define AMD_CPPC_HIGHEST_PERF_MASK GENMASK(31, 24) +#define AMD_CPPC_FLOOR_PERF_CNT_MASK GENMASK_ULL(39, 32) /* Masks for use with MSR_AMD_CPPC_REQ */ #define AMD_CPPC_MAX_PERF_MASK GENMASK(7, 0) @@ -778,6 +785,9 @@ #define AMD_CPPC_DES_PERF_MASK GENMASK(23, 16) #define AMD_CPPC_EPP_PERF_MASK GENMASK(31, 24) +/* Masks for use with MSR_AMD_CPPC_REQ2 */ +#define AMD_CPPC_FLOOR_PERF_MASK GENMASK(7, 0) + /* AMD Performance Counter Global Status and Control MSRs */ #define MSR_AMD64_PERF_CNTR_GLOBAL_STATUS 0xc0000300 #define MSR_AMD64_PERF_CNTR_GLOBAL_CTL 0xc0000301 @@ -889,6 +899,7 @@ #define MSR_K7_HWCR_IRPERF_EN_BIT 30 #define MSR_K7_HWCR_IRPERF_EN BIT_ULL(MSR_K7_HWCR_IRPERF_EN_BIT) #define MSR_K7_HWCR_CPUID_USER_DIS_BIT 35 +#define MSR_K7_HWCR_CPUID_USER_DIS BIT_ULL(MSR_K7_HWCR_CPUID_USER_DIS_BIT) #define MSR_K7_FID_VID_CTL 0xc0010041 #define MSR_K7_FID_VID_STATUS 0xc0010042 #define MSR_K7_HWCR_CPB_DIS_BIT 25 diff --git a/tools/arch/x86/include/uapi/asm/kvm.h b/tools/arch/x86/include/uapi/asm/kvm.h index 0d4538fa6c31..1585ec804066 100644 --- a/tools/arch/x86/include/uapi/asm/kvm.h +++ b/tools/arch/x86/include/uapi/asm/kvm.h @@ -197,13 +197,13 @@ struct kvm_msrs { __u32 nmsrs; /* number of msrs in entries */ __u32 pad; - struct kvm_msr_entry entries[]; + __DECLARE_FLEX_ARRAY(struct kvm_msr_entry, entries); }; /* for KVM_GET_MSR_INDEX_LIST */ struct kvm_msr_list { __u32 nmsrs; /* number of msrs in entries */ - __u32 indices[]; + __DECLARE_FLEX_ARRAY(__u32, indices); }; /* Maximum size of any access bitmap in bytes */ @@ -245,7 +245,7 @@ struct kvm_cpuid_entry { struct kvm_cpuid { __u32 nent; __u32 padding; - struct kvm_cpuid_entry entries[]; + __DECLARE_FLEX_ARRAY(struct kvm_cpuid_entry, entries); }; struct kvm_cpuid_entry2 { @@ -267,7 +267,7 @@ struct kvm_cpuid_entry2 { struct kvm_cpuid2 { __u32 nent; __u32 padding; - struct kvm_cpuid_entry2 entries[]; + __DECLARE_FLEX_ARRAY(struct kvm_cpuid_entry2, entries); }; /* for KVM_GET_PIT and KVM_SET_PIT */ @@ -398,7 +398,7 @@ struct kvm_xsave { * the contents of CPUID leaf 0xD on the host. */ __u32 region[1024]; - __u32 extra[]; + __DECLARE_FLEX_ARRAY(__u32, extra); }; #define KVM_MAX_XCRS 16 @@ -477,6 +477,7 @@ struct kvm_sync_regs { #define KVM_X86_QUIRK_STUFF_FEATURE_MSRS (1 << 8) #define KVM_X86_QUIRK_IGNORE_GUEST_PAT (1 << 9) #define KVM_X86_QUIRK_VMCS12_ALLOW_FREEZE_IN_SMM (1 << 10) +#define KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT (1 << 11) #define KVM_STATE_NESTED_FORMAT_VMX 0 #define KVM_STATE_NESTED_FORMAT_SVM 1 @@ -532,6 +533,7 @@ struct kvm_svm_nested_state_data { struct kvm_svm_nested_state_hdr { __u64 vmcb_pa; + __u64 gpat; }; /* for KVM_CAP_NESTED_STATE */ @@ -566,7 +568,7 @@ struct kvm_pmu_event_filter { __u32 fixed_counter_bitmap; __u32 flags; __u32 pad[4]; - __u64 events[]; + __DECLARE_FLEX_ARRAY(__u64, events); }; #define KVM_PMU_EVENT_ALLOW 0 diff --git a/tools/arch/x86/include/uapi/asm/svm.h b/tools/arch/x86/include/uapi/asm/svm.h index 650e3256ea7d..010a45c9f614 100644 --- a/tools/arch/x86/include/uapi/asm/svm.h +++ b/tools/arch/x86/include/uapi/asm/svm.h @@ -103,38 +103,38 @@ #define SVM_EXIT_VMGEXIT 0x403 /* SEV-ES software-defined VMGEXIT events */ -#define SVM_VMGEXIT_MMIO_READ 0x80000001 -#define SVM_VMGEXIT_MMIO_WRITE 0x80000002 -#define SVM_VMGEXIT_NMI_COMPLETE 0x80000003 -#define SVM_VMGEXIT_AP_HLT_LOOP 0x80000004 -#define SVM_VMGEXIT_AP_JUMP_TABLE 0x80000005 +#define SVM_VMGEXIT_MMIO_READ 0x80000001ull +#define SVM_VMGEXIT_MMIO_WRITE 0x80000002ull +#define SVM_VMGEXIT_NMI_COMPLETE 0x80000003ull +#define SVM_VMGEXIT_AP_HLT_LOOP 0x80000004ull +#define SVM_VMGEXIT_AP_JUMP_TABLE 0x80000005ull #define SVM_VMGEXIT_SET_AP_JUMP_TABLE 0 #define SVM_VMGEXIT_GET_AP_JUMP_TABLE 1 -#define SVM_VMGEXIT_PSC 0x80000010 -#define SVM_VMGEXIT_GUEST_REQUEST 0x80000011 -#define SVM_VMGEXIT_EXT_GUEST_REQUEST 0x80000012 -#define SVM_VMGEXIT_AP_CREATION 0x80000013 +#define SVM_VMGEXIT_PSC 0x80000010ull +#define SVM_VMGEXIT_GUEST_REQUEST 0x80000011ull +#define SVM_VMGEXIT_EXT_GUEST_REQUEST 0x80000012ull +#define SVM_VMGEXIT_AP_CREATION 0x80000013ull #define SVM_VMGEXIT_AP_CREATE_ON_INIT 0 #define SVM_VMGEXIT_AP_CREATE 1 #define SVM_VMGEXIT_AP_DESTROY 2 -#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018 -#define SVM_VMGEXIT_SAVIC 0x8000001a +#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018ull +#define SVM_VMGEXIT_SAVIC 0x8000001aull #define SVM_VMGEXIT_SAVIC_REGISTER_GPA 0 #define SVM_VMGEXIT_SAVIC_UNREGISTER_GPA 1 #define SVM_VMGEXIT_SAVIC_SELF_GPA ~0ULL -#define SVM_VMGEXIT_HV_FEATURES 0x8000fffd -#define SVM_VMGEXIT_TERM_REQUEST 0x8000fffe +#define SVM_VMGEXIT_HV_FEATURES 0x8000fffdull +#define SVM_VMGEXIT_TERM_REQUEST 0x8000fffeull #define SVM_VMGEXIT_TERM_REASON(reason_set, reason_code) \ /* SW_EXITINFO1[3:0] */ \ (((((u64)reason_set) & 0xf)) | \ /* SW_EXITINFO1[11:4] */ \ ((((u64)reason_code) & 0xff) << 4)) -#define SVM_VMGEXIT_UNSUPPORTED_EVENT 0x8000ffff +#define SVM_VMGEXIT_UNSUPPORTED_EVENT 0x8000ffffull /* Exit code reserved for hypervisor/software use */ -#define SVM_EXIT_SW 0xf0000000 +#define SVM_EXIT_SW 0xf0000000ull -#define SVM_EXIT_ERR -1 +#define SVM_EXIT_ERR -1ull #define SVM_EXIT_REASONS \ { SVM_EXIT_READ_CR0, "read_cr0" }, \ diff --git a/tools/include/linux/filter.h b/tools/include/linux/filter.h index bcc6df79301a..4ead4e72097c 100644 --- a/tools/include/linux/filter.h +++ b/tools/include/linux/filter.h @@ -6,6 +6,7 @@ #define __TOOLS_LINUX_FILTER_H #include <linux/bpf.h> +#include <uapi/linux/filter.h> /* ArgX, context and stack frame pointer register positions. Note, * Arg1, Arg2, Arg3, etc are used as argument mappings of function diff --git a/tools/include/linux/gfp_types.h b/tools/include/linux/gfp_types.h index 6c75df30a281..54ca0c88bab6 100644 --- a/tools/include/linux/gfp_types.h +++ b/tools/include/linux/gfp_types.h @@ -273,17 +273,17 @@ enum { * * %__GFP_ZERO returns a zeroed page on success. * - * %__GFP_ZEROTAGS zeroes memory tags at allocation time if the memory itself - * is being zeroed (either via __GFP_ZERO or via init_on_alloc, provided that - * __GFP_SKIP_ZERO is not set). This flag is intended for optimization: setting - * memory tags at the same time as zeroing memory has minimal additional - * performance impact. + * %__GFP_ZEROTAGS zeroes memory tags at allocation time. Setting memory tags at + * the same time as zeroing memory (e.g., with __GFP_ZERO) has minimal + * additional performance impact. However, __GFP_ZEROTAGS also zeroes the tags + * even if memory is not getting zeroed at allocation time (e.g., + * with init_on_free). * * %__GFP_SKIP_KASAN makes KASAN skip unpoisoning on page allocation. * Used for userspace and vmalloc pages; the latter are unpoisoned by - * kasan_unpoison_vmalloc instead. For userspace pages, results in - * poisoning being skipped as well, see should_skip_kasan_poison for - * details. Only effective in HW_TAGS mode. + * kasan_unpoison_vmalloc instead. If passed to vmalloc, kasan_unpoison_vmalloc + * is skipped too. For userspace pages, results in poisoning being skipped as + * well, see should_skip_kasan_poison for details. Only effective in HW_TAGS mode. */ #define __GFP_NOWARN ((__force gfp_t)___GFP_NOWARN) #define __GFP_COMP ((__force gfp_t)___GFP_COMP) diff --git a/tools/include/linux/objtool_types.h b/tools/include/linux/objtool_types.h index c6def4049b1a..c24e9ea39269 100644 --- a/tools/include/linux/objtool_types.h +++ b/tools/include/linux/objtool_types.h @@ -2,7 +2,7 @@ #ifndef _LINUX_OBJTOOL_TYPES_H #define _LINUX_OBJTOOL_TYPES_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include <linux/types.h> @@ -18,7 +18,7 @@ struct unwind_hint { u8 signal; }; -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ /* * UNWIND_HINT_TYPE_UNDEFINED: A blind spot in ORC coverage which can result in diff --git a/tools/include/linux/overflow.h b/tools/include/linux/overflow.h index 3427d7880326..98963688143f 100644 --- a/tools/include/linux/overflow.h +++ b/tools/include/linux/overflow.h @@ -1,4 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 OR MIT */ +#include <stdint.h> #ifndef __LINUX_OVERFLOW_H #define __LINUX_OVERFLOW_H diff --git a/tools/include/linux/static_call_types.h b/tools/include/linux/static_call_types.h index cfb6ddeb292b..ac55bc966a56 100644 --- a/tools/include/linux/static_call_types.h +++ b/tools/include/linux/static_call_types.h @@ -25,7 +25,7 @@ #define STATIC_CALL_SITE_INIT 2UL /* init section */ #define STATIC_CALL_SITE_FLAGS 3UL -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ /* * The static call site table needs to be created by external tooling (objtool @@ -102,6 +102,6 @@ struct static_call_key { #endif /* CONFIG_HAVE_STATIC_CALL */ -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #endif /* _STATIC_CALL_TYPES_H */ diff --git a/tools/include/linux/types.h b/tools/include/linux/types.h index d41f8a261bce..b6c473b7920d 100644 --- a/tools/include/linux/types.h +++ b/tools/include/linux/types.h @@ -23,6 +23,11 @@ typedef enum { __GFP_HIGH } gfp_t; +#ifdef __SIZEOF_INT128__ +typedef __signed__ __int128 __s128 __attribute__((aligned(16))); +typedef unsigned __int128 __u128 __attribute__((aligned(16))); +#endif + /* * We define u64 as uint64_t for every architecture * so that we can print it with "%"PRIx64 without getting warnings. diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index 00fd2e566d75..6d213d372bf8 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -17,7 +17,7 @@ endif # it defaults to this nolibc directory. OUTPUT ?= $(CURDIR)/ -architectures := arm arm64 loongarch m68k mips openrisc parisc powerpc riscv s390 sh sparc x86 +architectures := alpha arm arm64 loongarch m68k mips openrisc parisc powerpc riscv s390 sh sparc x86 arch_files := arch.h $(addsuffix .h, $(addprefix arch-, $(architectures))) all_files := \ alloca.h \ diff --git a/tools/include/nolibc/arch-alpha.h b/tools/include/nolibc/arch-alpha.h new file mode 100644 index 000000000000..04ff8a95c52c --- /dev/null +++ b/tools/include/nolibc/arch-alpha.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * Alpha specific definitions for NOLIBC + * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net> + */ + +#ifndef _NOLIBC_ARCH_ALPHA_H +#define _NOLIBC_ARCH_ALPHA_H + +#include "compiler.h" +#include "crt.h" + +/* + * Syscalls for Alpha: + * - registers are 64-bit + * - syscall number is passed in $0/v0 + * - the system call is performed by calling callsys + * - syscall return comes in $0/v0, error flag in $19/a3 + * - arguments are passed in $16/a0 to $21/a5 + * - GCC does not support symbol register names + */ + +#define _NOLIBC_SYSCALL_CLOBBERLIST \ + "$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", \ + "$22", "$23", "$24", "$25", "$27", "$28", "memory", "cc" + +#define __nolibc_syscall0(num) \ +({ \ + register long _num __asm__ ("$0") = (num); \ + register long _err __asm__ ("$19"); \ + \ + __asm__ volatile ( \ + "callsys" \ + : "+r"(_num), "=r"(_err) \ + : \ + : _NOLIBC_SYSCALL_CLOBBERLIST, \ + "$16", "$17", "$18", "$20", "$21" \ + ); \ + _err ? -_num : _num; \ +}) + +#define __nolibc_syscall1(num, arg1) \ +({ \ + register long _num __asm__ ("$0") = (num); \ + register long _err __asm__ ("$19"); \ + register long _arg1 __asm__ ("$16") = (long)(arg1); \ + \ + __asm__ volatile ( \ + "callsys" \ + : "+r"(_num), "=r"(_err) \ + : "r"(_arg1) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, "$17", "$18", "$20", "$21" \ + ); \ + _err ? -_num : _num; \ +}) + +#define __nolibc_syscall2(num, arg1, arg2) \ +({ \ + register long _num __asm__ ("$0") = (num); \ + register long _err __asm__ ("$19"); \ + register long _arg1 __asm__ ("$16") = (long)(arg1); \ + register long _arg2 __asm__ ("$17") = (long)(arg2); \ + \ + __asm__ volatile ( \ + "callsys" \ + : "+r"(_num), "=r"(_err) \ + : "r"(_arg1), "r"(_arg2) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, "$18", "$20", "$21" \ + ); \ + _err ? -_num : _num; \ +}) + +#define __nolibc_syscall3(num, arg1, arg2, arg3) \ +({ \ + register long _num __asm__ ("$0") = (num); \ + register long _err __asm__ ("$19"); \ + register long _arg1 __asm__ ("$16") = (long)(arg1); \ + register long _arg2 __asm__ ("$17") = (long)(arg2); \ + register long _arg3 __asm__ ("$18") = (long)(arg3); \ + \ + __asm__ volatile ( \ + "callsys" \ + : "+r"(_num), "=r"(_err) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, "$20", "$21" \ + ); \ + _err ? -_num : _num; \ +}) + +#define __nolibc_syscall4(num, arg1, arg2, arg3, arg4) \ +({ \ + register long _num __asm__ ("$0") = (num); \ + register long _arg1 __asm__ ("$16") = (long)(arg1); \ + register long _arg2 __asm__ ("$17") = (long)(arg2); \ + register long _arg3 __asm__ ("$18") = (long)(arg3); \ + register long _arg4 __asm__ ("$19") = (long)(arg4); \ + \ + __asm__ volatile ( \ + "callsys" \ + : "+r"(_num), "+r"(_arg4) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, "$20", "$21" \ + ); \ + _arg4 ? -_num : _num; \ +}) + +#define __nolibc_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ +({ \ + register long _num __asm__ ("$0") = (num); \ + register long _arg1 __asm__ ("$16") = (long)(arg1); \ + register long _arg2 __asm__ ("$17") = (long)(arg2); \ + register long _arg3 __asm__ ("$18") = (long)(arg3); \ + register long _arg4 __asm__ ("$19") = (long)(arg4); \ + register long _arg5 __asm__ ("$20") = (long)(arg5); \ + \ + __asm__ volatile ( \ + "callsys" \ + : "+r"(_num), "+r"(_arg4) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg5) \ + : _NOLIBC_SYSCALL_CLOBBERLIST, "$21" \ + ); \ + _arg4 ? -_num : _num; \ +}) + +#define __nolibc_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ +({ \ + register long _num __asm__ ("$0") = (num); \ + register long _arg1 __asm__ ("$16") = (long)(arg1); \ + register long _arg2 __asm__ ("$17") = (long)(arg2); \ + register long _arg3 __asm__ ("$18") = (long)(arg3); \ + register long _arg4 __asm__ ("$19") = (long)(arg4); \ + register long _arg5 __asm__ ("$20") = (long)(arg5); \ + register long _arg6 __asm__ ("$21") = (long)(arg6); \ + \ + __asm__ volatile ( \ + "callsys" \ + : "+r"(_num), "+r"(_arg4) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg5), \ + "r"(_arg6) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg4 ? -_num : _num; \ +}) + +/* startup code */ +void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector +_start(void) +{ + __asm__ volatile ( + "br $gp, 0f\n" /* setup $gp, so that 'lda' works */ + "0: ldgp $gp, 0($gp)\n" + "lda $27, _start_c\n" /* setup current function address for _start_c */ + "mov $sp, $16\n" /* save argc pointer to $16, as arg1 of _start_c */ + "br _start_c\n" /* transfer to c runtime */ + ); + __nolibc_entrypoint_epilogue(); +} + +#endif /* _NOLIBC_ARCH_ALPHA_H */ diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h index 8681922e05ca..b88686fb19c4 100644 --- a/tools/include/nolibc/arch-arm.h +++ b/tools/include/nolibc/arch-arm.h @@ -33,10 +33,7 @@ * with r7 before calling svc, and r6 is marked as clobbered. * We're just using any regular register which we assign to r7 after saving * it. - * - * Also, ARM supports the old_select syscall if newselect is not available */ -#define __ARCH_WANT_SYS_OLD_SELECT #if (defined(__THUMBEB__) || defined(__THUMBEL__)) && \ !defined(NOLIBC_OMIT_FRAME_POINTER) diff --git a/tools/include/nolibc/arch-powerpc.h b/tools/include/nolibc/arch-powerpc.h index a1ab91d55384..dbe2e5205aaa 100644 --- a/tools/include/nolibc/arch-powerpc.h +++ b/tools/include/nolibc/arch-powerpc.h @@ -26,7 +26,7 @@ */ #define _NOLIBC_SYSCALL_CLOBBERLIST \ - "memory", "cr0", "r12", "r11", "r10", "r9" + "memory", "cr0", "ctr", "xer", "r12", "r11", "r10", "r9" #define __nolibc_syscall0(num) \ ({ \ diff --git a/tools/include/nolibc/arch-sparc.h b/tools/include/nolibc/arch-sparc.h index ddae9bc10dfe..23fab40accfa 100644 --- a/tools/include/nolibc/arch-sparc.h +++ b/tools/include/nolibc/arch-sparc.h @@ -45,7 +45,7 @@ \ __asm__ volatile ( \ _NOLIBC_SYSCALL \ - : "+r"(_arg1) \ + : "=r"(_arg1) \ : "r"(_num) \ : "memory", "cc" \ ); \ diff --git a/tools/include/nolibc/arch-x86.h b/tools/include/nolibc/arch-x86.h index fe152ac2650b..577e9b682eb3 100644 --- a/tools/include/nolibc/arch-x86.h +++ b/tools/include/nolibc/arch-x86.h @@ -25,10 +25,7 @@ * don't have to experience issues with register constraints. * - the syscall number is always specified last in order to allow to force * some registers before (gcc refuses a %-register at the last position). - * - * Also, i386 supports the old_select syscall if newselect is not available */ -#define __ARCH_WANT_SYS_OLD_SELECT #define __nolibc_syscall0(num) \ ({ \ diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index b69d9c5ec5c6..06cc2dbe7a33 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -32,6 +32,8 @@ #include "arch-openrisc.h" #elif defined(__hppa__) #include "arch-parisc.h" +#elif defined(__alpha__) +#include "arch-alpha.h" #else #error Unsupported Architecture #endif diff --git a/tools/include/nolibc/unistd.h b/tools/include/nolibc/unistd.h index 79599ceef45d..a264a20da13d 100644 --- a/tools/include/nolibc/unistd.h +++ b/tools/include/nolibc/unistd.h @@ -73,6 +73,48 @@ int ftruncate(int fd, off_t length) return __sysret(_sys_ftruncate(fd, length)); } +/* + * char *getcwd(char *buf, size_t size); + */ + +static __attribute__((unused)) +int _sys_getcwd(char *buf, size_t size) +{ + return __nolibc_syscall2(__NR_getcwd, buf, size); +} + +static __attribute__((unused)) +char *getcwd(char *buf, size_t size) +{ + int ret; + + /* Unlike other libc's we don't handle passing NULL for buf */ + if (!buf || !size) { + SET_ERRNO(EINVAL); + return NULL; + } + + ret = __sysret(_sys_getcwd(buf, size)); + + /* On error return NULL, __sysret() above will have set errno */ + if (ret < 0) + return NULL; + + /* Handle no path being written or the kernel putting + * "(unreachable)" into the buffer instead of a path. + * This matches what musl is doing. + */ + if (ret == 0 || buf[0] != '/') { + SET_ERRNO(ENOENT); + return NULL; + } + + /* ret must be the number of bytes written at this point, + * so return the pointer to buf. + */ + return buf; +} + static __attribute__((unused)) int msleep(unsigned int msecs) { @@ -86,6 +128,22 @@ int msleep(unsigned int msecs) return 0; } +/* + * ssize_t readlink(const char *path, char *buf, size_t bufsiz); + */ + +static __attribute__((unused)) +ssize_t _sys_readlink(const char *path, char *buf, size_t bufsiz) +{ + return __nolibc_syscall4(__NR_readlinkat, AT_FDCWD, path, buf, bufsiz); +} + +static __attribute__((unused)) +ssize_t readlink(const char *path, char *buf, size_t bufsiz) +{ + return __sysret(_sys_readlink(path, buf, bufsiz)); +} + static __attribute__((unused)) unsigned int sleep(unsigned int seconds) { diff --git a/tools/include/uapi/asm-generic/unistd.h b/tools/include/uapi/asm-generic/unistd.h index a627acc8fb5f..5b7e77a7c736 100644 --- a/tools/include/uapi/asm-generic/unistd.h +++ b/tools/include/uapi/asm-generic/unistd.h @@ -863,8 +863,12 @@ __SYSCALL(__NR_listns, sys_listns) #define __NR_rseq_slice_yield 471 __SYSCALL(__NR_rseq_slice_yield, sys_rseq_slice_yield) +/* fs/open.c */ +#define __NR_fchroot 472 +__SYSCALL(__NR_fchroot, sys_fchroot) + #undef __NR_syscalls -#define __NR_syscalls 472 +#define __NR_syscalls 473 /* * 32 bit systems traditionally used different diff --git a/tools/include/uapi/linux/const.h b/tools/include/uapi/linux/const.h index b8f629ef135f..565f309b9df8 100644 --- a/tools/include/uapi/linux/const.h +++ b/tools/include/uapi/linux/const.h @@ -50,4 +50,22 @@ #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +/* + * Divide positive or negative dividend by positive or negative divisor + * and round to closest integer. Result is undefined for negative + * divisors if the dividend variable type is unsigned and for negative + * dividends if the divisor variable type is unsigned. + */ +#define __KERNEL_DIV_ROUND_CLOSEST(x, divisor) \ +({ \ + __typeof__(x) __x = x; \ + __typeof__(divisor) __d = divisor; \ + \ + (((__typeof__(x))-1) > 0 || \ + ((__typeof__(divisor))-1) > 0 || \ + (((__x) > 0) == ((__d) > 0))) ? \ + (((__x) + ((__d) / 2)) / (__d)) : \ + (((__x) - ((__d) / 2)) / (__d)); \ +}) + #endif /* _UAPI_LINUX_CONST_H */ diff --git a/tools/include/uapi/linux/fscrypt.h b/tools/include/uapi/linux/fscrypt.h index 3aff99f2696a..84507280b3ea 100644 --- a/tools/include/uapi/linux/fscrypt.h +++ b/tools/include/uapi/linux/fscrypt.h @@ -30,7 +30,6 @@ #define FSCRYPT_MODE_SM4_CTS 8 #define FSCRYPT_MODE_ADIANTUM 9 #define FSCRYPT_MODE_AES_256_HCTR2 10 -/* If adding a mode number > 10, update FSCRYPT_MODE_MAX in fscrypt_private.h */ /* * Legacy policy version; ad-hoc KDF and no key verification. diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h index d0c0c8605976..419011097fa8 100644 --- a/tools/include/uapi/linux/kvm.h +++ b/tools/include/uapi/linux/kvm.h @@ -11,6 +11,7 @@ #include <linux/const.h> #include <linux/types.h> #include <linux/compiler.h> +#include <linux/stddef.h> #include <linux/ioctl.h> #include <asm/kvm.h> @@ -542,7 +543,7 @@ struct kvm_coalesced_mmio { struct kvm_coalesced_mmio_ring { __u32 first, last; - struct kvm_coalesced_mmio coalesced_mmio[]; + __DECLARE_FLEX_ARRAY(struct kvm_coalesced_mmio, coalesced_mmio); }; #define KVM_COALESCED_MMIO_MAX \ @@ -592,7 +593,7 @@ struct kvm_clear_dirty_log { /* for KVM_SET_SIGNAL_MASK */ struct kvm_signal_mask { __u32 len; - __u8 sigset[]; + __DECLARE_FLEX_ARRAY(__u8, sigset); }; /* for KVM_TPR_ACCESS_REPORTING */ @@ -703,6 +704,11 @@ struct kvm_enable_cap { #define KVM_VM_TYPE_ARM_IPA_SIZE_MASK 0xffULL #define KVM_VM_TYPE_ARM_IPA_SIZE(x) \ ((x) & KVM_VM_TYPE_ARM_IPA_SIZE_MASK) + +#define KVM_VM_TYPE_ARM_PROTECTED (1UL << 31) +#define KVM_VM_TYPE_ARM_MASK (KVM_VM_TYPE_ARM_IPA_SIZE_MASK | \ + KVM_VM_TYPE_ARM_PROTECTED) + /* * ioctls for /dev/kvm fds: */ @@ -989,6 +995,8 @@ struct kvm_enable_cap { #define KVM_CAP_ARM_SEA_TO_USER 245 #define KVM_CAP_S390_USER_OPEREXEC 246 #define KVM_CAP_S390_KEYOP 247 +#define KVM_CAP_S390_VSIE_ESAMODE 248 +#define KVM_CAP_S390_HPAGE_2G 249 struct kvm_irq_routing_irqchip { __u32 irqchip; @@ -1051,7 +1059,7 @@ struct kvm_irq_routing_entry { struct kvm_irq_routing { __u32 nr; __u32 flags; - struct kvm_irq_routing_entry entries[]; + __DECLARE_FLEX_ARRAY(struct kvm_irq_routing_entry, entries); }; #define KVM_IRQFD_FLAG_DEASSIGN (1 << 0) @@ -1142,7 +1150,7 @@ struct kvm_dirty_tlb { struct kvm_reg_list { __u64 n; /* number of regs */ - __u64 reg[]; + __DECLARE_FLEX_ARRAY(__u64, reg); }; struct kvm_one_reg { @@ -1224,6 +1232,8 @@ enum kvm_device_type { #define KVM_DEV_TYPE_LOONGARCH_EIOINTC KVM_DEV_TYPE_LOONGARCH_EIOINTC KVM_DEV_TYPE_LOONGARCH_PCHPIC, #define KVM_DEV_TYPE_LOONGARCH_PCHPIC KVM_DEV_TYPE_LOONGARCH_PCHPIC + KVM_DEV_TYPE_LOONGARCH_DMSINTC, +#define KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_LOONGARCH_DMSINTC KVM_DEV_TYPE_ARM_VGIC_V5, #define KVM_DEV_TYPE_ARM_VGIC_V5 KVM_DEV_TYPE_ARM_VGIC_V5 @@ -1610,7 +1620,7 @@ struct kvm_stats_desc { #ifdef __KERNEL__ char name[KVM_STATS_NAME_SIZE]; #else - char name[]; + __DECLARE_FLEX_ARRAY(char, name); #endif }; diff --git a/tools/include/uapi/linux/rtnetlink.h b/tools/include/uapi/linux/rtnetlink.h index dab9493c791b..27265fd31e5f 100644 --- a/tools/include/uapi/linux/rtnetlink.h +++ b/tools/include/uapi/linux/rtnetlink.h @@ -840,6 +840,7 @@ enum { #define RTEXT_FILTER_CFM_CONFIG (1 << 5) #define RTEXT_FILTER_CFM_STATUS (1 << 6) #define RTEXT_FILTER_MST (1 << 7) +#define RTEXT_FILTER_NAME_ONLY (1 << 8) /* End of information exported to user level */ diff --git a/tools/lib/bpf/features.c b/tools/lib/bpf/features.c index b7e388f99d0b..e5641fa60163 100644 --- a/tools/lib/bpf/features.c +++ b/tools/lib/bpf/features.c @@ -577,10 +577,10 @@ static int probe_ldimm64_full_range_off(int token_fd) static int probe_uprobe_syscall(int token_fd) { /* - * If kernel supports uprobe() syscall, it will return -ENXIO when called + * If kernel supports uprobe() syscall, it will return -EPROTO when called * from the outside of a kernel-generated uprobe trampoline. */ - return syscall(__NR_uprobe) < 0 && errno == ENXIO; + return syscall(__NR_uprobe) < 0 && errno == EPROTO; } #else static int probe_uprobe_syscall(int token_fd) diff --git a/tools/lib/bpf/usdt.c b/tools/lib/bpf/usdt.c index 57fb82bb81b5..d2ecd3daab96 100644 --- a/tools/lib/bpf/usdt.c +++ b/tools/lib/bpf/usdt.c @@ -305,7 +305,7 @@ struct usdt_manager *usdt_manager_new(struct bpf_object *obj) /* * Detect kernel support for uprobe() syscall, it's presence means we can - * take advantage of faster nop5 uprobe handling. + * take advantage of faster nop10 uprobe handling. * Added in: 56101b69c919 ("uprobes/x86: Add uprobe syscall to speed up uprobe") */ man->has_uprobe_syscall = kernel_supports(obj, FEAT_UPROBE_SYSCALL); @@ -604,14 +604,14 @@ static int parse_usdt_spec(struct usdt_spec *spec, const struct usdt_note *note, #if defined(__x86_64__) static bool has_nop_combo(int fd, long off) { - unsigned char nop_combo[6] = { - 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x00 /* nop,nop5 */ + unsigned char nop_combo[11] = { + 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, }; - unsigned char buf[6]; + unsigned char buf[11]; - if (pread(fd, buf, 6, off) != 6) + if (pread(fd, buf, 11, off) != 11) return false; - return memcmp(buf, nop_combo, 6) == 0; + return memcmp(buf, nop_combo, 11) == 0; } #else static bool has_nop_combo(int fd, long off) @@ -822,8 +822,8 @@ static int collect_usdt_targets(struct usdt_manager *man, struct elf_fd *elf_fd, memset(target, 0, sizeof(*target)); /* - * We have uprobe syscall and usdt with nop,nop5 instructions combo, - * so we can place the uprobe directly on nop5 (+1) and get this probe + * We have uprobe syscall and usdt with nop,nop10 instructions combo, + * so we can place the uprobe directly on nop10 (+1) and get this probe * optimized. */ if (man->has_uprobe_syscall && has_nop_combo(elf_fd->fd, usdt_rel_ip)) { diff --git a/tools/objtool/Build b/tools/objtool/Build index 93a37b0dfd31..59f948628098 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -6,13 +6,15 @@ objtool-y += check.o objtool-y += special.o objtool-y += builtin-check.o objtool-y += elf.o +objtool-y += klp-symid.o objtool-y += objtool.o objtool-$(BUILD_DISAS) += disas.o objtool-$(BUILD_DISAS) += trace.o objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o -objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o klp-post-link.o +objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o \ + klp-post-link.o klp-sympos.o objtool-y += libstring.o objtool-y += libctype.o diff --git a/tools/objtool/arch/x86/special.c b/tools/objtool/arch/x86/special.c index e817a3fff449..1e84c81bfcd8 100644 --- a/tools/objtool/arch/x86/special.c +++ b/tools/objtool/arch/x86/special.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <string.h> +#include <arch/special.h> #include <objtool/special.h> #include <objtool/builtin.h> #include <objtool/warn.h> @@ -9,6 +10,32 @@ /* cpu feature name array generated from cpufeatures.h */ #include "cpu-feature-names.c" +/* + * An alternative with an empty replacement, e.g. the second entry of + * + * ALTERNATIVE_2("orig", "repl", ft1, "", ft2) + * + * still gets a relocation for its replacement offset. But the label it points + * at is the end of the previous entry's replacement, which is also the + * beginning of the *next* entry's replacement. The value is meaningless: it's + * only ever used with a length of zero. + */ +bool arch_alt_ignore_new_reloc(struct section *sec, unsigned long offset) +{ + unsigned long entry_off; + + if (strcmp(sec->name, ".altinstructions")) + return false; + + entry_off = offset - (offset % ALT_ENTRY_SIZE); + + if (offset - entry_off != ALT_NEW_OFFSET) + return false; + + return !*(unsigned char *)(sec->data->d_buf + entry_off + + ALT_NEW_LEN_OFFSET); +} + void arch_handle_alternative(struct special_alt *alt) { static struct special_alt *group, *prev; diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 118c3de2f293..75b11dc85010 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -76,6 +76,7 @@ static const struct option check_options[] = { OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"), OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks), OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), + OPT_BOOLEAN(0, "klp-symids", &opts.klp_symids, "generate .klp.symids for duplicate symbol disambiguation"), OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"), OPT_BOOLEAN(0, "noabs", &opts.noabs, "reject absolute references in allocatable sections"), OPT_BOOLEAN('n', "noinstr", &opts.noinstr, "validate noinstr rules"), @@ -174,10 +175,16 @@ static bool opts_valid(void) return false; } + if (opts.klp_symids && !opts.link) { + ERROR("--klp-symids requires --link"); + return false; + } + if (opts.disas || opts.hack_jump_label || opts.hack_noinstr || opts.ibt || + opts.klp_symids || opts.mcount || opts.noabs || opts.noinstr || diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 10b18cf9c360..288a48dff11c 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -15,6 +15,7 @@ #include <objtool/arch.h> #include <objtool/disas.h> #include <objtool/check.h> +#include <objtool/klp.h> #include <objtool/special.h> #include <objtool/trace.h> #include <objtool/warn.h> @@ -194,6 +195,7 @@ static bool is_rust_noreturn(const struct symbol *func) */ return str_ends_with(func->name, "_4core3num20from_str_radix_panic") || str_ends_with(func->name, "_4core3num22from_ascii_radix_panic") || + str_ends_with(func->name, "_4core3num28from_ascii_bytes_radix_panic") || str_ends_with(func->name, "_4core5sliceSp15copy_from_slice17len_mismatch_fail") || str_ends_with(func->name, "_4core6option13expect_failed") || str_ends_with(func->name, "_4core6option13unwrap_failed") || @@ -206,6 +208,7 @@ static bool is_rust_noreturn(const struct symbol *func) str_ends_with(func->name, "_4core9panicking18panic_nounwind_fmt") || str_ends_with(func->name, "_4core9panicking19assert_failed_inner") || str_ends_with(func->name, "_4core9panicking30panic_null_pointer_dereference") || + str_ends_with(func->name, "_4core9panicking32panic_null_reference_constructed") || str_ends_with(func->name, "_4core9panicking36panic_misaligned_pointer_dereference") || str_ends_with(func->name, "_7___rustc17rust_begin_unwind") || strstr(func->name, "_4core9panicking13assert_failed") || @@ -4922,6 +4925,12 @@ int check(struct objtool_file *file) goto out; } + if (opts.klp_symids) { + ret = klp_create_symid_sections(file); + if (ret) + goto out; + } + if (opts.noabs) warnings += check_abs_references(file); diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 33c95a74a51b..a791f4ea6ec1 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -23,6 +23,7 @@ #include <linux/log2.h> #include <objtool/builtin.h> #include <objtool/elf.h> +#include <objtool/klp.h> #include <objtool/warn.h> static ssize_t demangled_name_len(const char *name); @@ -626,6 +627,18 @@ static int read_symbols(struct elf *elf) return -1; } + /* + * "klp diff" renames the placeholder symbols of KLP relocs to + * hide them from modpost. Hide the prefix from the rest of + * objtool so its many name-based heuristics (noreturns, + * uaccess safe list, ...) still see the original symbol name. + * + * st_name is left alone, so the renamed symbol is preserved in + * the output file. + */ + if (strstarts(sym->name, KLP_TOMBSTONE_PREFIX)) + sym->name += strlen(KLP_TOMBSTONE_PREFIX); + if ((sym->sym.st_shndx > SHN_UNDEF && sym->sym.st_shndx < SHN_LORESERVE) || (shndx_data && sym->sym.st_shndx == SHN_XINDEX)) { diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index e844e9c82b7b..349690bb1c50 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -16,6 +16,7 @@ struct opts { bool hack_noinstr; bool hack_skylake; bool ibt; + bool klp_symids; bool mcount; bool noabs; bool noinstr; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index d9c44df9cc76..a82517a76a0f 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -97,6 +97,7 @@ struct symbol { u8 included : 1; u8 klp : 1; u8 dont_correlate : 1; + u8 fake : 1; struct list_head pv_target; struct reloc *relocs; struct section *group_sec; diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index 6f60cf05db86..c57775d78c71 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -14,21 +14,48 @@ #define KLP_FUNCS_SEC ".init.klp_funcs" /* - * __klp_relocs is an intermediate section which are created by klp diff and - * converted into KLP symbols/relas by "objtool klp post-link". This is needed - * to work around the linker, which doesn't preserve SHN_LIVEPATCH or + * __klp_relocs.<objname> are intermediate sections which are created by klp + * diff and converted into KLP symbols/relas by "objtool klp post-link". This + * is needed to work around the linker, which doesn't preserve SHN_LIVEPATCH or * SHF_RELA_LIVEPATCH, nor does it support having two RELA sections for a * single PROGBITS section. + * + * "objname" is the object whose loading gates the relocation: "vmlinux" for + * references to vmlinux symbols, otherwise the name of the module being + * patched. post-link uses it to name the resulting + * .klp.rela.objname.section_name sections. */ #define KLP_RELOCS_SEC "__klp_relocs" #define KLP_STRINGS_SEC ".rodata.klp.str1.1" +#define KLP_TOMBSTONE_PREFIX ".klp.tombstone." + struct klp_reloc { void *offset; void *sym; u32 type; }; +/* + * .klp.symid is used to correlate symbols between vmlinux.o and vmlinux, for + * calculating sympos to disambiguate duplicately-named symbols. + */ +#define KLP_SYMID_SEC ".klp.symid" + +struct klp_symid { + u64 id; + u64 addr; +}; + +struct objtool_file; +struct elf; +struct symbol; + +int klp_create_symid_sections(struct objtool_file *file); + +int klp_sympos_init(struct elf *orig); +unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym); + int cmd_klp_checksum(int argc, const char **argv); int cmd_klp_diff(int argc, const char **argv); int cmd_klp_post_link(int argc, const char **argv); diff --git a/tools/objtool/include/objtool/special.h b/tools/objtool/include/objtool/special.h index 121c3761899c..620dbf6cb0e5 100644 --- a/tools/objtool/include/objtool/special.h +++ b/tools/objtool/include/objtool/special.h @@ -32,6 +32,13 @@ int special_get_alts(struct elf *elf, struct list_head *alts); void arch_handle_alternative(struct special_alt *alt); +/* + * Should the reloc at @offset -- the "new" (replacement) field of a special + * section group entry -- be ignored? The meaning of a zero-length replacement + * is arch specific, so the arch decides. + */ +bool arch_alt_ignore_new_reloc(struct section *sec, unsigned long offset); + bool arch_support_alt_relocation(struct special_alt *special_alt, struct instruction *insn, struct reloc *reloc); diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index f8787d7d1454..16681a76f13d 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -12,7 +12,7 @@ #include <objtool/arch.h> #include <objtool/klp.h> #include <objtool/util.h> -#include <arch/special.h> +#include <objtool/special.h> #include <linux/align.h> #include <linux/objtool_types.h> @@ -30,7 +30,9 @@ struct elfs { struct export { struct hlist_node hash; - char *mod, *sym; + char *mod; + char *sym; + bool mod_ns; }; bool debug, debug_correlate, debug_clone; @@ -83,11 +85,40 @@ static char *escape_str(const char *orig) return new; } +/* + * Convert a build-tree object path to a runtime module name: strip + * directory components, replace '-' with '_', and remove file + * extensions. Examples: + * + * "arch/x86/kvm/kvm" -> "kvm" + * "arch/x86/kvm/kvm-intel" -> "kvm_intel". + * + * Used by read_exports() to normalize Module.symvers entries and by + * __find_modname() as a fallback when .modinfo lacks a "name=" tag. + */ +static char *normalize_modname(char *name) +{ + char *slash = strrchr(name, '/'); + + if (slash) + name = slash + 1; + + for (char *c = name; *c; c++) { + if (*c == '-') + *c = '_'; + else if (*c == '.') { + *c = '\0'; + break; + } + } + return name; +} + static int read_exports(void) { const char *symvers = "Module.symvers"; char line[1024], *path = NULL; - unsigned int line_num = 1; + unsigned int line_num = 0; FILE *file; file = fopen(symvers, "r"); @@ -106,9 +137,11 @@ static int read_exports(void) } while (fgets(line, 1024, file)) { - char *sym, *mod, *type; + char *sym, *mod, *type, *namespace; struct export *export; + line_num++; + sym = strchr(line, '\t'); if (!sym) { ERROR("malformed Module.symvers (sym) at line %d", line_num); @@ -133,6 +166,14 @@ static int read_exports(void) *type++ = '\0'; + namespace = strchr(type, '\t'); + if (!namespace) { + ERROR("malformed Module.symvers (namespace) at line %d", line_num); + return -1; + } + + *namespace++ = '\0'; + if (*sym == '\0' || *mod == '\0') { ERROR("malformed Module.symvers at line %d", line_num); return -1; @@ -150,12 +191,18 @@ static int read_exports(void) return -1; } + if (strcmp(export->mod, "vmlinux")) + export->mod = normalize_modname(export->mod); + export->sym = strdup(sym); if (!export->sym) { ERROR_GLIBC("strdup"); return -1; } + /* EXPORT_SYMBOL_FOR_MODULES() */ + export->mod_ns = strstarts(namespace, "module:"); + hash_add(exports, &export->hash, str_hash(sym)); } @@ -866,65 +913,6 @@ static int correlate_symbols(struct elfs *e) return 0; } -/* "sympos" is used by livepatch to disambiguate duplicate symbol names */ -static unsigned long find_sympos(struct elf *elf, struct symbol *sym) -{ - bool vmlinux = str_ends_with(objname, "vmlinux.o"); - unsigned long sympos = 0, nr_matches = 0; - bool has_dup = false; - struct symbol *s; - - if (sym->bind != STB_LOCAL) - return 0; - - if (vmlinux && is_func_sym(sym)) { - /* - * HACK: Unfortunately, symbol ordering can differ between - * vmlinux.o and vmlinux due to the linker script emitting - * .text.unlikely* before .text*. Count .text.unlikely* first. - * - * TODO: Disambiguate symbols more reliably (checksums?) - */ - for_each_sym(elf, s) { - if (strstarts(s->sec->name, ".text.unlikely") && - !strcmp(s->name, sym->name)) { - nr_matches++; - if (s == sym) - sympos = nr_matches; - else - has_dup = true; - } - } - for_each_sym(elf, s) { - if (!strstarts(s->sec->name, ".text.unlikely") && - !strcmp(s->name, sym->name)) { - nr_matches++; - if (s == sym) - sympos = nr_matches; - else - has_dup = true; - } - } - } else { - for_each_sym(elf, s) { - if (!strcmp(s->name, sym->name)) { - nr_matches++; - if (s == sym) - sympos = nr_matches; - else - has_dup = true; - } - } - } - - if (!sympos) { - ERROR("can't find sympos for %s", sym->name); - return ULONG_MAX; - } - - return has_dup ? sympos : 0; -} - static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym); static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym, @@ -1129,6 +1117,9 @@ static struct export *find_export(struct symbol *sym) { struct export *export; + if (is_local_sym(sym)) + return NULL; + hash_for_each_possible(exports, export, hash, str_hash(sym->name)) { if (!strcmp(export->sym, sym->name)) return export; @@ -1158,18 +1149,7 @@ static const char *__find_modname(struct elfs *e) return NULL; } - for (char *c = name; *c; c++) { - if (*c == '/') - name = c + 1; - else if (*c == '-') - *c = '_'; - else if (*c == '.') { - *c = '\0'; - break; - } - } - - return name; + return normalize_modname(name); } /* Get the object's module name as defined by the kernel (and klp_object) */ @@ -1210,11 +1190,16 @@ static bool klp_reloc_needed(struct reloc *patched_reloc) * clusterfunk that is late module patching, the patch module is * allowed to be loaded before any modules it depends on. * - * If exported by vmlinux, a normal reloc will do. + * If exported by vmlinux to all modules, a normal reloc will do. */ export = find_export(patched_sym); - if (export) - return strcmp(export->mod, "vmlinux"); + if (export) { + if (strcmp(export->mod, "vmlinux")) + return true; + + /* EXPORT_SYMBOL_FOR_MODULES() gets a klp reloc */ + return export->mod_ns; + } if (!patched_sym->twin) { /* @@ -1331,39 +1316,79 @@ static int convert_reloc_sym(struct elf *elf, struct reloc *reloc) } /* + * Check if the original module already has a dependency on dep_mod, i.e. it + * already references at least one export from that module. + */ +static bool has_module_dep(struct elfs *e, const char *dep_mod) +{ + struct symbol *sym; + + for_each_sym(e->orig, sym) { + struct export *exp; + + if (!is_undef_sym(sym) || is_weak_sym(sym)) + continue; + + exp = find_export(sym); + if (exp && !strcmp(exp->mod, dep_mod)) + return true; + } + + return false; +} + +/* * Convert a regular relocation to a klp relocation (sort of). */ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, struct section *sec, unsigned long offset, struct export *export) { + const char *sym_modname, *sym_orig_name, *sec_objname; struct symbol *patched_sym = patched_reloc->sym; s64 addend = reloc_addend(patched_reloc); - const char *sym_modname, *sym_orig_name; - static struct section *klp_relocs; + char tombstone_name[SYM_NAME_LEN]; struct symbol *sym, *klp_sym; unsigned long klp_reloc_off; + struct section *klp_relocs; + char sec_name[SEC_NAME_LEN]; char sym_name[SYM_NAME_LEN]; struct klp_reloc klp_reloc; unsigned long sympos; if (!patched_sym->twin) { - ERROR("unexpected klp reloc for new symbol %s", patched_sym->name); - return -1; + if (!export) { + ERROR("unexpected klp reloc for new symbol %s", patched_sym->name); + return -1; + } + + if (strcmp(export->mod, "vmlinux") && + !has_module_dep(e, export->mod)) { + ERROR("%s: new reference to %s (exported by %s) would create an undeclared module dependency", + patched_sym->name, export->sym, export->mod); + return -1; + } } /* * Keep the original reloc intact for now to avoid breaking objtool run * which relies on proper relocations for many of its features. This - * will be disabled later by "objtool klp post-link". + * reloc now targets a functionally dead tombstone symbol and will be + * disabled later by "objtool klp post-link". * - * Convert it to UNDEF (and WEAK to avoid modpost warnings). + * Convert the symbol to UNDEF/WEAK and rename to + * .klp.tombstone.sym_name to prevent modpost from printing warnings or + * creating false module dependencies. The prefix is hidden from the + * objtool run itself by read_symbols(). */ sym = patched_sym->clone; if (!sym) { - /* STB_WEAK: avoid modpost undefined symbol warnings */ - sym = elf_create_symbol(e->out, patched_sym->name, NULL, + if (snprintf_check(tombstone_name, SYM_NAME_LEN, + KLP_TOMBSTONE_PREFIX "%s", patched_sym->name)) + return -1; + + sym = elf_create_symbol(e->out, tombstone_name, NULL, STB_WEAK, patched_sym->type, 0, 0); if (!sym) return -1; @@ -1389,7 +1414,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, return -1; sym_orig_name = patched_sym->twin->name; - sympos = find_sympos(e->orig, patched_sym->twin); + sympos = klp_find_sympos(e->orig, patched_sym->twin); if (sympos == ULONG_MAX) return -1; } @@ -1411,16 +1436,35 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, } /* - * Create the __klp_relocs entry. This will be converted to an actual - * KLP rela by "objtool klp post-link". + * Create the __klp_relocs.<objname> entry. This will be converted to + * an actual KLP rela by "objtool klp post-link". * * This intermediate step is necessary to prevent corruption by the * linker, which doesn't know how to properly handle two rela sections * applying to the same base section. + * + * The objname decides when the reloc gets applied. A reference to a + * vmlinux symbol goes in the vmlinux section so it gets applied when + * the patch module loads. Everything else goes in the patched + * object's section, applied when the patched module is loaded. */ + if (!strcmp(sym_modname, "vmlinux")) { + sec_objname = "vmlinux"; + } else { + sec_objname = find_modname(e); + if (!sec_objname) + return -1; + } + + /* section format: __klp_relocs.objname */ + if (snprintf_check(sec_name, SEC_NAME_LEN, + KLP_RELOCS_SEC ".%s", sec_objname)) + return -1; + + klp_relocs = find_section_by_name(e->out, sec_name); if (!klp_relocs) { - klp_relocs = elf_create_section(e->out, KLP_RELOCS_SEC, 0, + klp_relocs = elf_create_section(e->out, sec_name, 0, 0, SHT_PROGBITS, 8, SHF_ALLOC); if (!klp_relocs) return -1; @@ -1568,6 +1612,10 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym) !strcmp(patched_reloc->sym->sec->name, ".altinstr_aux")) continue; + if (arch_alt_ignore_new_reloc(patched_sym->sec, + reloc_offset(patched_reloc))) + continue; + ret = convert_reloc_sym(e->patched, patched_reloc); if (ret < 0) { ERROR_FUNC(patched_rsec->base, reloc_offset(patched_reloc), @@ -1591,6 +1639,7 @@ static int create_fake_symbol(struct elf *elf, struct section *sec, unsigned long offset, size_t size) { char name[SYM_NAME_LEN]; + struct symbol *sym; unsigned int type; static int ctr; char *c; @@ -1607,7 +1656,24 @@ static int create_fake_symbol(struct elf *elf, struct section *sec, * while still allowing objdump to disassemble it. */ type = is_text_sec(sec) ? STT_NOTYPE : STT_OBJECT; - return elf_create_symbol(elf, name, sec, STB_LOCAL, type, offset, size) ? 0 : -1; + + sym = elf_create_symbol(elf, name, sec, STB_LOCAL, type, offset, size); + if (!sym) + return -1; + + sym->fake = 1; + return 0; +} + +static bool has_fake_symbols(struct section *sec) +{ + struct symbol *sym; + + sec_for_each_sym(sec, sym) + if (sym->fake) + return true; + + return false; } /* @@ -1658,13 +1724,17 @@ static int create_fake_symbols(struct elf *elf) for_each_reloc(sec->rsec, reloc) { unsigned long offset, size; struct reloc *next_reloc; + bool last = true; if (annotype(elf, sec, reloc) != ANNOTYPE_DATA_SPECIAL) continue; offset = reloc_addend(reloc); - size = 0; + /* + * Find the start of the next entry so the fake symbol size can + * be calculated. + */ next_reloc = reloc; for_each_reloc_continue(sec->rsec, next_reloc) { if (annotype(elf, sec, next_reloc) != ANNOTYPE_DATA_SPECIAL || @@ -1672,10 +1742,15 @@ static int create_fake_symbols(struct elf *elf) continue; size = reloc_addend(next_reloc) - offset; + last = false; break; } - if (!size) + /* + * If no next entry found, this is the last entry, so its size + * is from the current offset to the end of the section. + */ + if (last) size = sec_size(reloc->sym->sec) - offset; if (create_fake_symbol(elf, reloc->sym->sec, offset, size)) @@ -1690,7 +1765,11 @@ entsize: unsigned int entry_size; unsigned long offset; - if (!is_special_section(sec) || find_symbol_by_offset(sec, 0)) + if (!is_special_section(sec)) + continue; + + /* Skip sections already handled by step 1 above */ + if (has_fake_symbols(sec)) continue; if (!sec->rsec) { @@ -2007,7 +2086,7 @@ static int create_klp_sections(struct elfs *e) /* klp_func_ext.sympos */ BUILD_BUG_ON(sizeof(sympos) != sizeof_field(struct klp_func_ext, sympos)); - sympos = find_sympos(e->orig, sym->clone->twin); + sympos = klp_find_sympos(e->orig, sym->clone->twin); if (sympos == ULONG_MAX) return -1; memcpy(func_data + offsetof(struct klp_func_ext, sympos), &sympos, @@ -2161,6 +2240,9 @@ int cmd_klp_diff(int argc, const char **argv) if (!e.orig || !e.patched) return -1; + if (klp_sympos_init(e.orig)) + return -1; + if (read_exports()) return -1; diff --git a/tools/objtool/klp-post-link.c b/tools/objtool/klp-post-link.c index c013e39957b1..350d20495897 100644 --- a/tools/objtool/klp-post-link.c +++ b/tools/objtool/klp-post-link.c @@ -19,19 +19,11 @@ #include <objtool/util.h> #include <linux/livepatch_external.h> -static int fix_klp_relocs(struct elf *elf) +static int fix_klp_reloc_sec(struct elf *elf, struct section *symtab, + struct section *klp_relocs) { - struct section *symtab, *klp_relocs; - - klp_relocs = find_section_by_name(elf, KLP_RELOCS_SEC); - if (!klp_relocs) - return 0; - - symtab = find_section_by_name(elf, ".symtab"); - if (!symtab) { - ERROR("missing .symtab"); - return -1; - } + /* section format: __klp_relocs.sec_objname */ + const char *sec_objname = klp_relocs->name + strlen(KLP_RELOCS_SEC "."); for (int i = 0; i < sec_size(klp_relocs) / sizeof(struct klp_reloc); i++) { struct klp_reloc *klp_reloc; @@ -39,7 +31,6 @@ static int fix_klp_relocs(struct elf *elf) struct section *sec, *tmp, *klp_rsec; unsigned long offset; struct reloc *reloc; - char sym_modname[64]; char rsec_name[SEC_NAME_LEN]; u64 addend; struct symbol *sym, *klp_sym; @@ -55,7 +46,7 @@ static int fix_klp_relocs(struct elf *elf) reloc = find_reloc_by_dest(elf, klp_relocs, klp_reloc_off + offsetof(struct klp_reloc, offset)); if (!reloc) { - ERROR("malformed " KLP_RELOCS_SEC " section"); + ERROR("malformed %s section", klp_relocs->name); return -1; } @@ -66,17 +57,13 @@ static int fix_klp_relocs(struct elf *elf) reloc = find_reloc_by_dest(elf, klp_relocs, klp_reloc_off + offsetof(struct klp_reloc, sym)); if (!reloc) { - ERROR("malformed " KLP_RELOCS_SEC " section"); + ERROR("malformed %s section", klp_relocs->name); return -1; } klp_sym = reloc->sym; addend = reloc_addend(reloc); - /* symbol format: .klp.sym.modname.sym_name,sympos */ - if (sscanf(klp_sym->name + strlen(KLP_SYM_PREFIX), "%55[^.]", sym_modname) != 1) - ERROR("can't find modname in klp symbol '%s'", klp_sym->name); - /* * Create the KLP rela: */ @@ -84,7 +71,7 @@ static int fix_klp_relocs(struct elf *elf) /* section format: .klp.rela.sec_objname.section_name */ if (snprintf_check(rsec_name, SEC_NAME_LEN, KLP_RELOC_SEC_PREFIX "%s.%s", - sym_modname, sec->name)) + sec_objname, sec->name)) return -1; klp_rsec = find_section_by_name(elf, rsec_name); @@ -134,10 +121,32 @@ static int fix_klp_relocs(struct elf *elf) return 0; } +static int fix_klp_relocs(struct elf *elf) +{ + struct section *symtab, *sec; + + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) { + ERROR("missing .symtab"); + return -1; + } + + for_each_sec(elf, sec) { + if (strncmp(sec->name, KLP_RELOCS_SEC ".", + strlen(KLP_RELOCS_SEC "."))) + continue; + + if (fix_klp_reloc_sec(elf, symtab, sec)) + return -1; + } + + return 0; +} + /* * This runs on the livepatch module after all other linking has been done. It - * converts the intermediate __klp_relocs section into proper KLP relocs to be - * processed by livepatch. This needs to run last to avoid linker wreckage. + * converts the intermediate __klp_relocs.* sections into proper KLP relocs to + * be processed by livepatch. This needs to run last to avoid linker wreckage. * Linkers don't tend to handle the "two rela sections for a single base * section" case very well, nor do they appreciate SHN_LIVEPATCH. */ diff --git a/tools/objtool/klp-symid.c b/tools/objtool/klp-symid.c new file mode 100644 index 000000000000..b19f76dff13e --- /dev/null +++ b/tools/objtool/klp-symid.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Emit the .klp.symid table which allows "objtool klp diff" to reliably + * disambiguate duplicate-named local symbols in vmlinux. + * + * Livepatch identifies a duplicate-named symbol by its position (sympos) + * among the same-named kallsyms entries, counted in ascending address order + * in the final linked vmlinux. That order can't be derived from vmlinux.o + * alone: the final link reorders sub-sections (.text.unlikely*, .data..*, + * etc). + * + * Bridge the gap with a table which survives the final link: a single + * non-alloc section containing an array of { id, addr } entries, where + * 'id' is a unique counter identifier and 'addr' has a relocation to the + * symbol. The linker copies 'id' verbatim and resolves 'addr' to the symbol's + * final address. + * + * The table is only emitted for vmlinux.o, and only when klp-build asks for it + * with KLP_SYMIDS=1, which adds --klp-symids to the vmlinux.o objtool run. + * + * It can't survive --gc-sections, which sweeps the whole section; klp-build + * rejects CONFIG_LD_DEAD_CODE_DATA_ELIMINATION. + */ +#include <linux/string.h> + +#include <objtool/objtool.h> +#include <objtool/warn.h> +#include <objtool/endianness.h> +#include <objtool/klp.h> + +static const char * const discarded_secs[] = { + ".discard", + ".exitcall.exit", + ".modinfo", + ".no_trim_symbol", + "__tracepoint_check", +}; + +static bool discarded_sec(struct section *sec) +{ + if (!(sec->sh.sh_flags & SHF_ALLOC)) + return true; + + for (int i = 0; i < ARRAY_SIZE(discarded_secs); i++) + if (strstarts(sec->name, discarded_secs[i])) + return true; + + return false; +} + +static bool symid_needed(struct elf *elf, struct symbol *sym) +{ + struct symbol *s; + + if (!is_local_sym(sym) || is_undef_sym(sym)) + return false; + + if (!is_func_sym(sym) && !is_object_sym(sym)) + return false; + + if (is_prefix_func(sym)) + return false; + + if (discarded_sec(sym->sec)) + return false; + + for_each_sym_by_name(elf, sym->name, s) { + if (s == sym || is_sec_sym(s) || is_file_sym(s) || is_undef_sym(s)) + continue; + return true; + } + + return false; +} + +int klp_create_symid_sections(struct objtool_file *file) +{ + struct elf *elf = file->elf; + struct klp_symid *symids; + struct section *sec; + struct symbol *sym; + u64 nr = 0, i = 0; + + if (!str_ends_with(objname, "vmlinux.o")) + return 0; + + for_each_sym(elf, sym) + if (symid_needed(elf, sym)) + nr++; + + if (!nr) + return 0; + + sec = elf_create_section(elf, KLP_SYMID_SEC, 0, sizeof(struct klp_symid), + SHT_PROGBITS, 8, 0); + if (!sec) + return -1; + + symids = elf_add_data(elf, sec, NULL, nr * sizeof(struct klp_symid)); + if (!symids) + return -1; + + for_each_sym(elf, sym) { + if (!symid_needed(elf, sym)) + continue; + + symids[i].id = bswap_if_needed(elf, i); + + if (!elf_create_reloc(elf, sec, + i * sizeof(struct klp_symid) + + offsetof(struct klp_symid, addr), + sym, 0, R_ABS64)) + return -1; + + i++; + } + + return 0; +} diff --git a/tools/objtool/klp-sympos.c b/tools/objtool/klp-sympos.c new file mode 100644 index 000000000000..dfca9dd74681 --- /dev/null +++ b/tools/objtool/klp-sympos.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Compute "sympos", the position used by livepatch to disambiguate + * duplicate symbol names in the patched object. + */ +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> + +#include <objtool/objtool.h> +#include <objtool/warn.h> +#include <objtool/endianness.h> +#include <objtool/klp.h> + +#include <linux/string.h> + +struct vmlinux_sym { + struct hlist_node hash; + const char *name; + u64 addr; +}; + +struct vmlinux_symid { + struct hlist_node hash; + u64 id; + u64 addr; +}; + +struct vmlinux_o_symid { + struct hlist_node hash; + u64 id; + unsigned int sym_idx; +}; + +static DEFINE_HASHTABLE(vmlinux_o_symids, 16); + +/* + * The original linked kernel, found next to the orig vmlinux.o. Read with raw + * libelf rather than elf_open_read(): only the symbol table and the resolved + * .klp.symid table are needed, not the (huge) instruction/reloc machinery. + * + * Both tables are built once by read_orig_vmlinux(). The Elf handle stays + * open because the hashed names point into its mmapped string table. + */ +static struct { + Elf *elf; + DECLARE_HASHTABLE(syms, 16); /* name -> address */ + DECLARE_HASHTABLE(symids, 16); /* .klp.symid id -> address */ +} vmlinux; + +/* + * Would the symbol be visible to the runtime's kallsyms-based symbol lookup? + */ +static bool vmlinux_sym_in_kallsyms(Elf *elf, GElf_Sym *sym) +{ + unsigned int type = GELF_ST_TYPE(sym->st_info); + GElf_Shdr shdr; + Elf_Scn *scn; + + if (sym->st_shndx == SHN_UNDEF || sym->st_shndx >= SHN_LORESERVE) + return false; + + if (type == STT_SECTION || type == STT_FILE) + return false; + + scn = elf_getscn(elf, sym->st_shndx); + if (!scn || !gelf_getshdr(scn, &shdr)) + return false; + + return shdr.sh_flags & SHF_ALLOC; +} + +static int read_orig_vmlinux(const char *filename) +{ + size_t shstrndx, nr_syms = 0, nr_symids = 0, strtab_idx = 0; + Elf_Data *symtab_data = NULL, *symid_data = NULL; + struct klp_symid *symids; + Elf_Scn *scn = NULL; + GElf_Ehdr ehdr; + int fd; + + fd = open(filename, O_RDONLY); + if (fd == -1) { + ERROR_GLIBC("can't open '%s'", filename); + return -1; + } + + if (elf_version(EV_CURRENT) == EV_NONE) { + ERROR_ELF("elf_version"); + return -1; + } + + vmlinux.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!vmlinux.elf) { + ERROR_ELF("elf_begin"); + return -1; + } + + if (!gelf_getehdr(vmlinux.elf, &ehdr)) { + ERROR_ELF("gelf_getehdr"); + return -1; + } + + if (elf_getshdrstrndx(vmlinux.elf, &shstrndx)) { + ERROR_ELF("elf_getshdrstrndx"); + return -1; + } + + while ((scn = elf_nextscn(vmlinux.elf, scn))) { + const char *name; + GElf_Shdr shdr; + + if (!gelf_getshdr(scn, &shdr)) { + ERROR_ELF("gelf_getshdr"); + return -1; + } + + if (shdr.sh_type == SHT_SYMTAB) { + symtab_data = elf_getdata(scn, NULL); + if (!symtab_data) { + ERROR_ELF("elf_getdata"); + return -1; + } + nr_syms = shdr.sh_size / shdr.sh_entsize; + strtab_idx = shdr.sh_link; + continue; + } + + name = elf_strptr(vmlinux.elf, shstrndx, shdr.sh_name); + if (name && !strcmp(name, KLP_SYMID_SEC)) { + if (shdr.sh_size % sizeof(struct klp_symid)) { + ERROR("%s: %s: struct klp_symid size mismatch", + filename, KLP_SYMID_SEC); + return -1; + } + symid_data = elf_getdata(scn, NULL); + if (!symid_data) { + ERROR_ELF("elf_getdata"); + return -1; + } + nr_symids = shdr.sh_size / sizeof(struct klp_symid); + } + } + + if (!symtab_data) { + ERROR("%s: missing symbol table", filename); + return -1; + } + + if (!symid_data) { + ERROR("%s: missing %s section, kernel not built with CONFIG_KLP_BUILD?", + filename, KLP_SYMID_SEC); + return -1; + } + + for (size_t i = 0; i < nr_syms; i++) { + struct vmlinux_sym *vsym; + const char *name; + GElf_Sym s; + + if (!gelf_getsym(symtab_data, i, &s)) { + ERROR_ELF("gelf_getsym"); + return -1; + } + + if (!vmlinux_sym_in_kallsyms(vmlinux.elf, &s)) + continue; + + name = elf_strptr(vmlinux.elf, strtab_idx, s.st_name); + if (!name) + continue; + + vsym = calloc(1, sizeof(*vsym)); + if (!vsym) { + ERROR_GLIBC("calloc"); + return -1; + } + + vsym->name = name; + vsym->addr = s.st_value; + hash_add(vmlinux.syms, &vsym->hash, str_hash(name)); + } + + symids = symid_data->d_buf; + + for (size_t i = 0; i < nr_symids; i++) { + struct vmlinux_symid *vsymid; + + vsymid = calloc(1, sizeof(*vsymid)); + if (!vsymid) { + ERROR_GLIBC("calloc"); + return -1; + } + + vsymid->id = __bswap_if_needed(&ehdr, symids[i].id); + vsymid->addr = __bswap_if_needed(&ehdr, symids[i].addr); + hash_add(vmlinux.symids, &vsymid->hash, vsymid->id); + } + + /* the fd and Elf handle stay open, the hashed names live in the mmap */ + return 0; +} + +/* + * Read the orig vmlinux.o's .klp.symid table, an array of entries whose 'addr' + * fields have relocs to the symbols they describe. + */ +static int read_vmlinux_o_symids(struct elf *vmlinux_o) +{ + struct section *sec; + + for_each_sec(vmlinux_o, sec) { + unsigned long nr; + + if (strcmp(sec->name, KLP_SYMID_SEC)) + continue; + + if (sec_size(sec) % sizeof(struct klp_symid)) { + ERROR("%s: %s: struct klp_symid size mismatch", + vmlinux_o->name, KLP_SYMID_SEC); + return -1; + } + + nr = sec_size(sec) / sizeof(struct klp_symid); + + for (unsigned long i = 0; i < nr; i++) { + unsigned long offset = i * sizeof(struct klp_symid); + struct vmlinux_o_symid *entry; + struct klp_symid *symid; + struct reloc *reloc; + + entry = calloc(1, sizeof(*entry)); + if (!entry) { + ERROR_GLIBC("calloc"); + return -1; + } + + symid = sec->data->d_buf + offset; + entry->id = bswap_if_needed(vmlinux_o, symid->id); + + reloc = find_reloc_by_dest(vmlinux_o, sec, + offset + offsetof(struct klp_symid, addr)); + if (!reloc) { + ERROR("%s: missing reloc for %s entry", + vmlinux_o->name, KLP_SYMID_SEC); + return -1; + } + entry->sym_idx = reloc->sym->idx; + + hash_add(vmlinux_o_symids, &entry->hash, entry->sym_idx); + } + } + + return 0; +} + +int klp_sympos_init(struct elf *orig) +{ + char *filename; + int ret; + + if (!str_ends_with(objname, "vmlinux.o")) + return 0; + + if (read_vmlinux_o_symids(orig)) + return -1; + + filename = strndup(objname, strlen(objname) - 2); + if (!filename) { + ERROR_GLIBC("strndup"); + return -1; + } + + ret = read_orig_vmlinux(filename); + free(filename); + + return ret; +} + +/* Find the symbol's id in the orig vmlinux.o's .klp.symid table */ +static int find_vmlinux_o_symid(struct symbol *sym, u64 *id) +{ + struct vmlinux_o_symid *entry; + + hash_for_each_possible(vmlinux_o_symids, entry, hash, sym->idx) { + if (entry->sym_idx == sym->idx) { + *id = entry->id; + return 0; + } + } + + ERROR("no %s entry for symbol %s in orig vmlinux.o", KLP_SYMID_SEC, + sym->name); + return -1; +} + +/* Find the symbol's final address in the orig vmlinux's .klp.symid table */ +static int find_vmlinux_symid_addr(u64 id, u64 *addr) +{ + struct vmlinux_symid *symid; + + hash_for_each_possible(vmlinux.symids, symid, hash, id) { + if (symid->id == id) { + *addr = symid->addr; + return 0; + } + } + + return -1; +} + +/* + * Find the sympos of a vmlinux-local symbol by ranking its final address + * among the duplicately named symbols in the linked orig vmlinux, replicating + * the order in which kallsyms_on_each_match_symbol() counts them. + */ +static unsigned long find_vmlinux_sympos(struct symbol *sym) +{ + unsigned long nr_matches = 0, sympos = 1; + u32 key = str_hash(sym->name); + struct vmlinux_sym *vsym; + bool found = false; + u64 id, addr; + + hash_for_each_possible(vmlinux.syms, vsym, hash, key) + if (!strcmp(vsym->name, sym->name)) + nr_matches++; + + if (!nr_matches) { + ERROR("can't find symbol %s in orig vmlinux", sym->name); + return ULONG_MAX; + } + + /* + * Unique symbols don't need disambiguating. They also have no + * .klp.symid entry, which is only emitted for names duplicated in + * vmlinux.o, so the lookups below would fail. + */ + if (nr_matches == 1) + return 0; + + if (find_vmlinux_o_symid(sym, &id)) + return ULONG_MAX; + + if (find_vmlinux_symid_addr(id, &addr)) { + ERROR("no %s entry for symbol %s in orig vmlinux", KLP_SYMID_SEC, + sym->name); + return ULONG_MAX; + } + + hash_for_each_possible(vmlinux.syms, vsym, hash, key) { + if (strcmp(vsym->name, sym->name)) + continue; + + if (vsym->addr < addr) + sympos++; + else if (vsym->addr == addr) + found = true; + } + + if (!found) { + ERROR("%s address mismatch for symbol %s, stale orig vmlinux?", + KLP_SYMID_SEC, sym->name); + return ULONG_MAX; + } + + return sympos; +} + +static bool is_init_sym(struct symbol *sym) +{ + return strstarts(sym->sec->name, ".init"); +} + +/* + * "sympos" is used by livepatch to disambiguate duplicate symbol names. + */ +unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym) +{ + unsigned long sympos = 0, nr_matches = 0; + bool has_dup = false; + struct symbol *s; + + if (is_init_sym(sym)) { + ERROR("%s: can't patch or reference init code/data", sym->name); + return ULONG_MAX; + } + + if (sym->bind != STB_LOCAL) + return 0; + + /* + * vmlinux: the final link reorders symbols relative to vmlinux.o, + * so the position needs to be derived from the linked orig vmlinux via + * the .klp.symid table. + */ + if (vmlinux.elf) + return find_vmlinux_sympos(sym); + + /* + * modules: the final .ko preserves symbol table order, so a + * symtab-order count here matches the runtime count done by + * module_kallsyms_on_each_symbol(). + */ + for_each_sym(elf, s) { + if (!strcmp(s->name, sym->name)) { + nr_matches++; + if (s == sym) + sympos = nr_matches; + else + has_dup = true; + } + } + + if (!sympos) { + ERROR("can't find sympos for %s", sym->name); + return ULONG_MAX; + } + + return has_dup ? sympos : 0; +} diff --git a/tools/perf/arch/arm/entry/syscalls/syscall.tbl b/tools/perf/arch/arm/entry/syscalls/syscall.tbl index 94351e22bfcf..55717ed32c27 100644 --- a/tools/perf/arch/arm/entry/syscalls/syscall.tbl +++ b/tools/perf/arch/arm/entry/syscalls/syscall.tbl @@ -486,3 +486,4 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot diff --git a/tools/perf/arch/mips/entry/syscalls/syscall_n64.tbl b/tools/perf/arch/mips/entry/syscalls/syscall_n64.tbl index 630aab9e5425..83dc93a0712f 100644 --- a/tools/perf/arch/mips/entry/syscalls/syscall_n64.tbl +++ b/tools/perf/arch/mips/entry/syscalls/syscall_n64.tbl @@ -386,3 +386,4 @@ 469 n64 file_setattr sys_file_setattr 470 n64 listns sys_listns 471 n64 rseq_slice_yield sys_rseq_slice_yield +472 n64 fchroot sys_fchroot diff --git a/tools/perf/arch/powerpc/entry/syscalls/syscall.tbl b/tools/perf/arch/powerpc/entry/syscalls/syscall.tbl index 4fcc7c58a105..cfbb70039ff0 100644 --- a/tools/perf/arch/powerpc/entry/syscalls/syscall.tbl +++ b/tools/perf/arch/powerpc/entry/syscalls/syscall.tbl @@ -562,3 +562,4 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 nospu rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot diff --git a/tools/perf/arch/s390/entry/syscalls/syscall.tbl b/tools/perf/arch/s390/entry/syscalls/syscall.tbl index 09a7ef04d979..1b45e68a217b 100644 --- a/tools/perf/arch/s390/entry/syscalls/syscall.tbl +++ b/tools/perf/arch/s390/entry/syscalls/syscall.tbl @@ -398,3 +398,4 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot diff --git a/tools/perf/arch/sh/entry/syscalls/syscall.tbl b/tools/perf/arch/sh/entry/syscalls/syscall.tbl index 70b315cbe710..ace068dff0de 100644 --- a/tools/perf/arch/sh/entry/syscalls/syscall.tbl +++ b/tools/perf/arch/sh/entry/syscalls/syscall.tbl @@ -475,3 +475,4 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot diff --git a/tools/perf/arch/sparc/entry/syscalls/syscall.tbl b/tools/perf/arch/sparc/entry/syscalls/syscall.tbl index 7e71bf7fcd14..5b9fe0e8140f 100644 --- a/tools/perf/arch/sparc/entry/syscalls/syscall.tbl +++ b/tools/perf/arch/sparc/entry/syscalls/syscall.tbl @@ -517,3 +517,4 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot diff --git a/tools/perf/arch/x86/entry/syscalls/syscall_32.tbl b/tools/perf/arch/x86/entry/syscalls/syscall_32.tbl index f832ebd2d79b..2c172ef48dfd 100644 --- a/tools/perf/arch/x86/entry/syscalls/syscall_32.tbl +++ b/tools/perf/arch/x86/entry/syscalls/syscall_32.tbl @@ -477,3 +477,4 @@ 469 i386 file_setattr sys_file_setattr 470 i386 listns sys_listns 471 i386 rseq_slice_yield sys_rseq_slice_yield +472 i386 fchroot sys_fchroot diff --git a/tools/perf/arch/x86/entry/syscalls/syscall_64.tbl b/tools/perf/arch/x86/entry/syscalls/syscall_64.tbl index 524155d655da..d5b6045b0090 100644 --- a/tools/perf/arch/x86/entry/syscalls/syscall_64.tbl +++ b/tools/perf/arch/x86/entry/syscalls/syscall_64.tbl @@ -396,6 +396,7 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot # # Due to a historical design error, certain syscalls are numbered differently diff --git a/tools/perf/arch/xtensa/entry/syscalls/syscall.tbl b/tools/perf/arch/xtensa/entry/syscalls/syscall.tbl index a9bca4e484de..d354bb231796 100644 --- a/tools/perf/arch/xtensa/entry/syscalls/syscall.tbl +++ b/tools/perf/arch/xtensa/entry/syscalls/syscall.tbl @@ -442,3 +442,4 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot diff --git a/tools/perf/bench/bpf_skel/.gitignore b/tools/perf/bench/bpf_skel/.gitignore new file mode 100644 index 000000000000..cd01455e1b53 --- /dev/null +++ b/tools/perf/bench/bpf_skel/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +.tmp +*.skel.h +vmlinux.h diff --git a/tools/perf/trace/beauty/include/linux/socket.h b/tools/perf/trace/beauty/include/linux/socket.h index 3ffdfe184b23..2a8d7b14f1d1 100644 --- a/tools/perf/trace/beauty/include/linux/socket.h +++ b/tools/perf/trace/beauty/include/linux/socket.h @@ -356,7 +356,7 @@ struct ucred { /* Flags to be cleared on entry by sendmsg and sendmmsg syscalls */ #define MSG_INTERNAL_SENDMSG_FLAGS \ - (MSG_SPLICE_PAGES | MSG_SENDPAGE_NOPOLICY | MSG_SENDPAGE_DECRYPTED) + (MSG_SPLICE_PAGES | MSG_SENDPAGE_NOPOLICY | MSG_SENDPAGE_DECRYPTED | MSG_NO_SHARED_FRAGS) /* Setsockoptions(2) level. Thanks to BSD these must match IPPROTO_xxx */ #define SOL_IP 0 diff --git a/tools/perf/trace/beauty/include/uapi/drm/drm.h b/tools/perf/trace/beauty/include/uapi/drm/drm.h index 27cc159c1d27..bc7ef7684099 100644 --- a/tools/perf/trace/beauty/include/uapi/drm/drm.h +++ b/tools/perf/trace/beauty/include/uapi/drm/drm.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: MIT */ /* * Header for the Direct Rendering Manager * @@ -11,25 +12,6 @@ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef _DRM_H_ @@ -1323,6 +1305,13 @@ extern "C" { */ #define DRM_IOCTL_MODE_GETFB2 DRM_IOWR(0xCE, struct drm_mode_fb_cmd2) +/** + * DRM_IOCTL_SYNCOBJ_EVENTFD - Register an eventfd to be signalled by a syncobj. + * + * This can be used to integrate a syncobj in an event loop. + * + * The IOCTL argument is a struct drm_syncobj_eventfd. + */ #define DRM_IOCTL_SYNCOBJ_EVENTFD DRM_IOWR(0xCF, struct drm_syncobj_eventfd) /** diff --git a/tools/perf/trace/beauty/include/uapi/linux/fs.h b/tools/perf/trace/beauty/include/uapi/linux/fs.h index 2ea4c81df08f..bd87262f2e34 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/fs.h +++ b/tools/perf/trace/beauty/include/uapi/linux/fs.h @@ -395,7 +395,16 @@ struct file_attr { #define FS_DAX_FL 0x02000000 /* Inode is DAX */ #define FS_INLINE_DATA_FL 0x10000000 /* Reserved for ext4 */ #define FS_PROJINHERIT_FL 0x20000000 /* Create with parents projid */ -#define FS_CASEFOLD_FL 0x40000000 /* Folder is case insensitive */ +/* + * FS_CASEFOLD_FL indicates case-insensitive name lookup. The + * bit is most often reported on directories, where it controls + * lookups of entries within. Filesystems that derive + * case-insensitivity from mount or volume state may also report + * it on non-directory inodes; userspace must not assume the bit + * is directory-only. FS_XFLAG_CASEFOLD reports the same + * information read-only via FS_IOC_FSGETXATTR. + */ +#define FS_CASEFOLD_FL 0x40000000 #define FS_RESERVED_FL 0x80000000 /* reserved for ext2 lib */ #define FS_FL_USER_VISIBLE 0x0003DFFF /* User visible flags */ diff --git a/tools/power/cpupower/Makefile b/tools/power/cpupower/Makefile index 969716dfe8de..ab428e336d87 100644 --- a/tools/power/cpupower/Makefile +++ b/tools/power/cpupower/Makefile @@ -131,7 +131,7 @@ override CFLAGS += -DVERSION=\"$(VERSION)\" -DPACKAGE=\"$(PACKAGE)\" \ UTIL_OBJS = utils/helpers/amd.o utils/helpers/msr.o \ utils/helpers/sysfs.o utils/helpers/misc.o utils/helpers/cpuid.o \ - utils/helpers/pci.o utils/helpers/bitmask.o \ + utils/helpers/pci.o utils/helpers/bitmask.o utils/helpers/cppc.o \ utils/idle_monitor/nhm_idle.o utils/idle_monitor/snb_idle.o \ utils/idle_monitor/hsw_ext_idle.o \ utils/idle_monitor/amd_fam14h_idle.o utils/idle_monitor/cpuidle_sysfs.o \ @@ -236,9 +236,9 @@ $(OUTPUT)%.o: %.c $(OUTPUT)cpupower: $(UTIL_OBJS) $(OUTPUT)$(LIBCPUPOWER) $(ECHO) " CC " $@ ifeq ($(strip $(STATIC)),true) - $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lrt -lpci -L$(OUTPUT) -o $@ + $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lm -lrt -lpci -L$(OUTPUT) -o $@ else - $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lcpupower -lrt -lpci -L$(OUTPUT) -o $@ + $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lm -lcpupower -lrt -lpci -L$(OUTPUT) -o $@ endif $(QUIET) $(STRIPCMD) $@ diff --git a/tools/power/cpupower/utils/cpufreq-info.c b/tools/power/cpupower/utils/cpufreq-info.c index 5a242b491a9d..11629ae49f98 100644 --- a/tools/power/cpupower/utils/cpufreq-info.c +++ b/tools/power/cpupower/utils/cpufreq-info.c @@ -270,10 +270,10 @@ static int get_freq_hardware(unsigned int cpu, unsigned int human) { unsigned long freq; - if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_APERF)) + freq = cpufreq_get_freq_hardware(cpu); + if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_APERF) && !freq) return -EINVAL; - freq = cpufreq_get_freq_hardware(cpu); printf(_(" current CPU frequency: ")); if (!freq) { printf("Unable to call hardware\n"); @@ -477,12 +477,13 @@ static int get_latency(unsigned int cpu, unsigned int human) } /* --performance / -c */ - static int get_perf_cap(unsigned int cpu) { if (cpupower_cpu_info.vendor == X86_VENDOR_AMD && cpupower_cpu_info.caps & CPUPOWER_CAP_AMD_PSTATE) amd_pstate_show_perf_and_freq(cpu, no_rounding); + else + cppc_show_perf_and_freq(cpu, no_rounding); return 0; } @@ -513,8 +514,8 @@ static void debug_output_one(unsigned int cpu) get_available_governors(cpu); get_policy(cpu); - if (get_freq_hardware(cpu, 1) < 0) - get_freq_kernel(cpu, 1); + get_freq_hardware(cpu, 1); + get_freq_kernel(cpu, 1); get_boost_mode(cpu); get_perf_cap(cpu); } diff --git a/tools/power/cpupower/utils/helpers/cppc.c b/tools/power/cpupower/utils/helpers/cppc.c new file mode 100644 index 000000000000..3493ce8551ea --- /dev/null +++ b/tools/power/cpupower/utils/helpers/cppc.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "helpers/helpers.h" +#include "cpufreq.h" +#include "acpi_cppc.h" + +#define cppc_to_frequency(perf) (roundf(slope * (perf) + intercept)) + +void cppc_show_perf_and_freq(unsigned int cpu, int no_rounding) +{ + int64_t nominal = acpi_cppc_get_data(cpu, NOMINAL_PERF); + int64_t nominal_freq = acpi_cppc_get_data(cpu, NOMINAL_FREQ) * 1000; + int64_t lowest = acpi_cppc_get_data(cpu, LOWEST_PERF); + int64_t lowest_freq = acpi_cppc_get_data(cpu, LOWEST_FREQ) * 1000; + unsigned long non_linear = acpi_cppc_get_data(cpu, LOWEST_NONLINEAR_PERF); + unsigned long highest = acpi_cppc_get_data(cpu, HIGHEST_PERF); + float slope, intercept; + + /* do the optional freq fields look invalid? */ + if (!nominal_freq || !lowest_freq || nominal == lowest) + return; + + slope = (float)(nominal_freq - lowest_freq) / (nominal - lowest); + intercept = lowest_freq - slope * lowest; + + printf(_(" CPPC limits:\n")); + printf(_(" Highest Performance: %lu. Maximum Frequency: "), + highest); + /* + * If boost isn't active, the cpuinfo_max doesn't indicate real max + * frequency. + */ + print_speed(cppc_to_frequency(highest), no_rounding); + printf(".\n"); + + printf(_(" Nominal Performance: %lu. Nominal Frequency: "), + acpi_cppc_get_data(cpu, NOMINAL_PERF)); + print_speed(nominal_freq, no_rounding); + printf(".\n"); + + printf(_(" Lowest Non-linear Performance: %lu. Lowest Non-linear Frequency: "), + non_linear); + print_speed(cppc_to_frequency(non_linear), no_rounding); + printf(".\n"); + + printf(_(" Lowest Performance: %lu. Lowest Frequency: "), + acpi_cppc_get_data(cpu, LOWEST_PERF)); + print_speed(lowest_freq, no_rounding); + printf(".\n"); +} diff --git a/tools/power/cpupower/utils/helpers/helpers.h b/tools/power/cpupower/utils/helpers/helpers.h index a3ad80b9c2c2..9c5126b63966 100644 --- a/tools/power/cpupower/utils/helpers/helpers.h +++ b/tools/power/cpupower/utils/helpers/helpers.h @@ -221,4 +221,6 @@ void print_online_cpus(void); void print_offline_cpus(void); void print_speed(unsigned long speed, int no_rounding); +void cppc_show_perf_and_freq(unsigned int cpu, int no_rounding); + #endif /* __CPUPOWERUTILS_HELPERS__ */ diff --git a/tools/power/cpupower/utils/powercap-info.c b/tools/power/cpupower/utils/powercap-info.c index e53033488218..88a5edb76315 100644 --- a/tools/power/cpupower/utils/powercap-info.c +++ b/tools/power/cpupower/utils/powercap-info.c @@ -47,8 +47,6 @@ static int powercap_print_one_zone(struct powercap_zone *zone) printf("\n"); - if (ret != 0) - return ret; return ret; } diff --git a/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py b/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py index 38cfbdcdedb7..64001bc80f68 100755 --- a/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py +++ b/tools/power/x86/intel_pstate_tracer/intel_pstate_tracer.py @@ -32,8 +32,6 @@ import re import signal import sys import getopt -import Gnuplot -from numpy import * from decimal import * __author__ = "Srinivas Pandruvada" @@ -88,8 +86,8 @@ def print_help(driver_name): print(' kbytes: Kilo bytes of memory per CPU to allocate to the trace buffer. Default: 10240') print(' Output:') print(' If not already present, creates a "results/test_name" folder in the current working directory with:') - print(' cpu.csv - comma seperated values file with trace contents and some additional calculations.') - print(' cpu???.csv - comma seperated values file for CPU number ???.') + print(' cpu.csv - comma separated values file with trace contents and some additional calculations.') + print(' cpu???.csv - comma separated values file for CPU number ???.') print(' *.png - a variety of PNG format plot files created from the trace contents and the additional calculations.') print(' Notes:') print(' Avoid the use of _ (underscore) in test names, because in gnuplot it is a subscript directive.') @@ -295,6 +293,8 @@ def common_all_gnuplot_settings(output_png): def common_gnuplot_settings(): """ common gnuplot settings. """ + import Gnuplot + g_plot = Gnuplot.Gnuplot(persist=1) # The following line is for rigor only. It seems to be assumed for .csv files g_plot('set datafile separator \",\"') @@ -343,7 +343,7 @@ def store_csv(cpu_int, time_pre_dec, time_post_dec, core_busy, scaled, _from, _t graph_data_present = True; def split_csv(current_max_cpu, cpu_mask): - """ seperate the all csv file into per CPU csv files. """ + """ separate the main csv file into per CPU csv files. """ if os.path.exists('cpu.csv'): for index in range(0, current_max_cpu + 1): @@ -482,7 +482,7 @@ def read_trace_data(filename, cpu_mask): if cpu_int > current_max_cpu: current_max_cpu = cpu_int # End of for each trace line loop -# Now seperate the main overall csv file into per CPU csv files. +# Now separate the main overall csv file into per CPU csv files. split_csv(current_max_cpu, cpu_mask) def signal_handler(signal, frame): @@ -508,8 +508,6 @@ if __name__ == "__main__": valid1 = False valid2 = False - cpu_mask = zeros((MAX_CPUS,), dtype=int) - try: opts, args = getopt.getopt(sys.argv[1:],"ht:i:c:n:m:",["help","trace_file=","interval=","cpu=","name=","memory="]) except getopt.GetoptError: @@ -538,6 +536,10 @@ if __name__ == "__main__": print_help('intel_pstate') sys.exit() + from numpy import zeros + + cpu_mask = zeros((MAX_CPUS,), dtype=int) + if cpu_list: for p in re.split("[,]", cpu_list): if int(p) < MAX_CPUS : diff --git a/tools/sched_ext/include/scx/cid.bpf.h b/tools/sched_ext/include/scx/cid.bpf.h index db247e42fb45..6b0b4e41b288 100644 --- a/tools/sched_ext/include/scx/cid.bpf.h +++ b/tools/sched_ext/include/scx/cid.bpf.h @@ -391,7 +391,9 @@ static __always_inline bool cmask_equal(const struct scx_cmask __arena *a, if (a->base != b->base || a->nr_cids != b->nr_cids) return false; - nr_words = CMASK_NR_WORDS(a->nr_cids); + if (a->nr_cids == 0) + return true; + nr_words = (a->base + a->nr_cids - 1) / 64 - a->base / 64 + 1; bpf_for(i, 0, CMASK_MAX_WORDS) { if (i >= nr_words) @@ -402,36 +404,6 @@ static __always_inline bool cmask_equal(const struct scx_cmask __arena *a, return true; } -/* - * True iff every bit set in @a is also set in @b over the intersection of - * their ranges. Bits of @a outside @b's range fail the test. - */ -static __always_inline bool cmask_subset(const struct scx_cmask __arena *a, - const struct scx_cmask __arena *b) -{ - u32 a_end = a->base + a->nr_cids; - u32 b_end = b->base + b->nr_cids; - u32 a_wbase = a->base / 64; - u32 b_wbase = b->base / 64; - u32 nr_words, i; - - /* any bit of @a outside @b's range is a subset violation */ - if (a->base < b->base || a_end > b_end) - return false; - - nr_words = CMASK_NR_WORDS(a->nr_cids); - bpf_for(i, 0, CMASK_MAX_WORDS) { - u32 wi_b; - - if (i >= nr_words) - break; - wi_b = a_wbase + i - b_wbase; - if (a->bits[i] & ~b->bits[wi_b]) - return false; - } - return true; -} - /** * cmask_next_set - find the first set bit at or after @cid * @m: cmask to search @@ -489,15 +461,65 @@ static __always_inline u32 cmask_first_set(const struct scx_cmask __arena *m) (cid) = cmask_next_set((m), (cid) + 1)) /* + * True iff every bit set in @a is also set in @b. Matches the kernel-side + * scx_cmask_subset(): ranges don't need to nest, and set bits of @a outside + * @b's range fail the test. + */ +static __always_inline bool cmask_subset(const struct scx_cmask __arena *a, + const struct scx_cmask __arena *b) +{ + u32 a_end = a->base + a->nr_cids; + u32 b_end = b->base + b->nr_cids; + u32 a_wbase = a->base / 64; + u32 b_wbase = b->base / 64; + u32 lo = a->base > b->base ? a->base : b->base; + u32 hi = a_end < b_end ? a_end : b_end; + u32 lo_word, hi_word, i; + + /* set bits of @a outside @b's range can't be in @b */ + if (a->base < b->base && + cmask_next_set(a, a->base) < (b->base < a_end ? b->base : a_end)) + return false; + if (a_end > b_end && + cmask_next_set(a, a->base > b_end ? a->base : b_end) < a_end) + return false; + + if (lo >= hi) + return true; + + /* + * Walk the words the range intersection spans. Plain word tests + * suffice: the scans above guarantee @a has no set bit outside @b's + * range and padding bits are kept clear by all cmask helpers. + */ + lo_word = lo / 64; + hi_word = (hi - 1) / 64; + + bpf_for(i, 0, CMASK_MAX_WORDS) { + u32 w = lo_word + i; + + if (w > hi_word) + break; + if (a->bits[w - a_wbase] & ~b->bits[w - b_wbase]) + return false; + } + return true; +} + +/* * Population count over [base, base + nr_cids). Padding bits in the head/tail * words are guaranteed zero by the mutating helpers, so a flat popcount over - * all words is correct. + * the words the range spans is correct. */ static __always_inline u32 cmask_weight(const struct scx_cmask __arena *m) { - u32 nr_words = CMASK_NR_WORDS(m->nr_cids), i; + u32 nr_words, i; u32 count = 0; + if (!m->nr_cids) + return 0; + nr_words = (m->base + m->nr_cids - 1) / 64 - m->base / 64 + 1; + bpf_for(i, 0, CMASK_MAX_WORDS) { if (i >= nr_words) break; diff --git a/tools/scripts/syscall.tbl b/tools/scripts/syscall.tbl index 7a42b32b6577..0ab531605120 100644 --- a/tools/scripts/syscall.tbl +++ b/tools/scripts/syscall.tbl @@ -412,3 +412,4 @@ 469 common file_setattr sys_file_setattr 470 common listns sys_listns 471 common rseq_slice_yield sys_rseq_slice_yield +472 common fchroot sys_fchroot diff --git a/tools/testing/cxl/test/mem.c b/tools/testing/cxl/test/mem.c index a1d170f88fee..a7da279aa3ef 100644 --- a/tools/testing/cxl/test/mem.c +++ b/tools/testing/cxl/test/mem.c @@ -2,7 +2,6 @@ // Copyright(c) 2021 Intel Corporation. All rights reserved. #include <linux/platform_device.h> -#include <linux/mod_devicetable.h> #include <linux/vmalloc.h> #include <linux/module.h> #include <linux/delay.h> diff --git a/tools/testing/kunit/configs/all_tests.config b/tools/testing/kunit/configs/all_tests.config index bccc2c77196d..6825c2e855a5 100644 --- a/tools/testing/kunit/configs/all_tests.config +++ b/tools/testing/kunit/configs/all_tests.config @@ -21,6 +21,7 @@ CONFIG_VFAT_FS=y CONFIG_PCI=y CONFIG_USB4=y CONFIG_I2C=y +CONFIG_GPIOLIB=y CONFIG_NET=y CONFIG_MCTP=y diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py index ac3f7159e67f..91d234ac3b57 100755 --- a/tools/testing/kunit/kunit.py +++ b/tools/testing/kunit/kunit.py @@ -126,7 +126,7 @@ def _list_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) lines.pop() # Filter out any extraneous non-test output that might have gotten mixed in. - return [l for l in output if re.match(r'^[^\s.]+\.[^\s.]+$', l)] + return [l for l in lines if re.match(r'^[^\s.]+\.[^\s.]+$', l)] def _list_tests_attr(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -> Iterable[str]: args = ['kunit.action=list_attr'] diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py index da88c3a1651d..85ae21754bdf 100755 --- a/tools/testing/kunit/kunit_tool_test.py +++ b/tools/testing/kunit/kunit_tool_test.py @@ -979,6 +979,18 @@ class KUnitMainTest(unittest.TestCase): self.linux_source_mock.run_kernel.assert_called_once_with( args=['kunit.action=list'], build_dir='.kunit', filter_glob='suite*', filter='', filter_action=None, timeout=300) + def test_list_tests_with_prefix(self): + want = ['suite.test1', 'suite.test2', 'suite2.test1'] + self.linux_source_mock.run_kernel.return_value = [ + '[ 0.100000] TAP version 14', + '[ 0.200000] suite.test1', + '[ 0.200000] suite.test2', + '[ 0.300000] suite2.test1'] + + got = kunit._list_tests(self.linux_source_mock, + kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False)) + self.assertEqual(got, want) + @mock.patch.object(kunit, '_list_tests') def test_run_isolated_by_suite(self, mock_tests): mock_tests.return_value = ['suite.test1', 'suite.test2', 'suite2.test1'] diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 8d4db2241cc2..8a4b6ddc68df 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -33,6 +33,7 @@ TARGETS += fchmodat2 TARGETS += filesystems TARGETS += filesystems/binderfs TARGETS += filesystems/epoll +TARGETS += filesystems/failfs TARGETS += filesystems/fat TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount @@ -42,6 +43,8 @@ TARGETS += filesystems/fuse TARGETS += filesystems/move_mount TARGETS += filesystems/empty_mntns TARGETS += filesystems/fsmount_ns +TARGETS += filesystems/fscontext_ns +TARGETS += filesystems/mntns_cleanup TARGETS += firmware TARGETS += fpu TARGETS += ftrace diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index d4f845c32804..a329f901c5ed 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -84,6 +84,7 @@ static void find_controls(void) if (err < 0) { ksft_print_msg("Failed to get hctl for card %d: %s\n", card, snd_strerror(err)); + free(card_data); goto next_card; } @@ -339,9 +340,9 @@ static bool ctl_value_index_valid(struct ctl_data *ctl, } if (int64_val > snd_ctl_elem_info_get_max64(ctl->info)) { - ksft_print_msg("%s.%d value %lld more than maximum %ld\n", + ksft_print_msg("%s.%d value %lld more than maximum %lld\n", ctl->name, index, int64_val, - snd_ctl_elem_info_get_max(ctl->info)); + snd_ctl_elem_info_get_max64(ctl->info)); return false; } diff --git a/tools/testing/selftests/arm64/abi/.gitignore b/tools/testing/selftests/arm64/abi/.gitignore index 44f8b80f37e3..491a80db9dff 100644 --- a/tools/testing/selftests/arm64/abi/.gitignore +++ b/tools/testing/selftests/arm64/abi/.gitignore @@ -1,4 +1,6 @@ hwcap ptrace +seccomp_ptrace_x0_bypass +seccomp_ret_trace_x0_bypass syscall-abi tpidr2 diff --git a/tools/testing/selftests/arm64/abi/Makefile b/tools/testing/selftests/arm64/abi/Makefile index 483488f8c2ad..e91d4cdf17ad 100644 --- a/tools/testing/selftests/arm64/abi/Makefile +++ b/tools/testing/selftests/arm64/abi/Makefile @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2021 ARM Limited -TEST_GEN_PROGS := hwcap ptrace syscall-abi tpidr2 +CFLAGS += $(KHDR_INCLUDES) -I$(top_srcdir)/tools/include + +TEST_GEN_PROGS := hwcap ptrace syscall-abi tpidr2 seccomp_ptrace_x0_bypass seccomp_ret_trace_x0_bypass include ../../lib.mk diff --git a/tools/testing/selftests/arm64/abi/seccomp_ptrace_x0_bypass.c b/tools/testing/selftests/arm64/abi/seccomp_ptrace_x0_bypass.c new file mode 100644 index 000000000000..4ee8e5aaad6f --- /dev/null +++ b/tools/testing/selftests/arm64/abi/seccomp_ptrace_x0_bypass.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test that seccomp, tracepoints and audit observe the correct syscall + * arguments after a ptracer has modified them at syscall-enter-stop. + * + * On arm64, both the first argument and the return value of a syscall + * are passed in register x0. The original x0 is saved in + * pt_regs::orig_x0 during syscall entry and returned as the first + * argument by syscall_get_arguments(). Because ptrace modifications + * to x0 are not automatically reflected in orig_x0, seccomp, tracepoints + * and audit may see a stale value unless orig_x0 is explicitly + * re-synchronised after a ptrace stop. + * + * This test sets up a seccomp filter that allows write(2, ...) but kills + * the task for any other fd. A ptracer changes the fd argument from 2 + * to 1 at the syscall-enter stop. If the orig_x0 re-sync works, seccomp + * sees the modified argument (fd=1) and kills the child with SIGSYS + * (test passes). If orig_x0 is not re-synced, seccomp sees the original + * fd=2, the write succeeds and the child exits normally (test fails, + * vulnerability present). + */ +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/prctl.h> +#include <sys/ptrace.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <asm/ptrace.h> +#include <linux/elf.h> +#include <linux/filter.h> +#include <linux/seccomp.h> +#include <asm/unistd.h> + +#include "kselftest.h" + +#define EXPECTED_TESTS 1 + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define ARG0_OFFSET (offsetof(struct seccomp_data, args)) +#else +#define ARG0_OFFSET (offsetof(struct seccomp_data, args) + 4) +#endif + +static int do_child(void) +{ + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) + ksft_exit_fail_perror("PTRACE_TRACEME"); + + if (raise(SIGSTOP)) + ksft_exit_fail_perror("raise(SIGSTOP)"); + + /* + * Seccomp filter: + * If syscall is not write -> ALLOW + * If syscall is write: + * - If args[0] (fd) == 2 -> ALLOW + * - Otherwise -> KILL + */ + struct sock_filter filter[] = { + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), /* nr */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 3), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, ARG0_OFFSET), /* args[0] */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 2, 1, 0), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + }; + struct sock_fprog prog = { + .len = ARRAY_SIZE(filter), + .filter = filter, + }; + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + ksft_exit_fail_perror("prctl NO_NEW_PRIVS"); + + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) + ksft_exit_fail_perror("prctl SECCOMP"); + + /* + * Invoke write(2, ...) while the tracer will change the first + * argument (fd) from 2 to 1 at syscall entry. + */ + syscall(__NR_write, 2, NULL, 0); + _exit(0); +} + +static int do_parent(pid_t child) +{ + bool bypass = false; + int status; + + /* Wait for the initial SIGSTOP */ + if (waitpid(child, &status, 0) != child) + ksft_exit_fail_msg("waitpid failed"); + + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) + ksft_exit_fail_msg("unexpected stop status"); + + if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_EXITKILL)) + ksft_exit_fail_perror("PTRACE_SETOPTIONS"); + + if (ptrace(PTRACE_SYSCALL, child, 0, 0)) + ksft_exit_fail_perror("PTRACE_SYSCALL"); + + while (1) { + int sig; + + if (waitpid(child, &status, 0) != child) + ksft_exit_fail_msg("waitpid lost child"); + + if (WIFEXITED(status)) { + /* Child exited normally – bypass succeeded */ + bypass = true; + break; + } + + if (WIFSIGNALED(status)) { + sig = WTERMSIG(status); + if (sig == SIGSYS) + break; + ksft_exit_fail_msg("child died unexpectedly from signal %d (%s)", + sig, strsignal(sig)); + } + + if (!WIFSTOPPED(status)) + ksft_exit_fail_msg("unexpected wait status"); + + sig = WSTOPSIG(status); + + if (sig == (SIGTRAP | 0x80)) { + struct user_regs_struct regs; + struct iovec iov = { + .iov_base = ®s, + .iov_len = sizeof(regs), + }; + + if (ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov)) + ksft_exit_fail_perror("PTRACE_GETREGSET"); + + unsigned long syscall_nr = regs.regs[8]; + unsigned long x0 = regs.regs[0]; + + /* Modify fd from 2 to 1 at write entry */ + if (syscall_nr == __NR_write && x0 == 2) { + regs.regs[0] = 1; + if (ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov)) + ksft_exit_fail_perror("PTRACE_SETREGSET"); + } + + if (ptrace(PTRACE_SYSCALL, child, 0, 0)) + ksft_exit_fail_perror("PTRACE_SYSCALL"); + } else { + /* Forward other signals */ + if (ptrace(PTRACE_SYSCALL, child, 0, sig)) + ksft_exit_fail_perror("PTRACE_SYSCALL"); + } + } + + /* bypass == true means vulnerability exists -> test fails */ + return bypass ? EXIT_FAILURE : EXIT_SUCCESS; +} + +int main(void) +{ + pid_t child; + + ksft_print_header(); + ksft_set_plan(EXPECTED_TESTS); + + child = fork(); + if (child < 0) + ksft_exit_fail_msg("fork failed: %s", strerror(errno)); + + if (!child) + return do_child(); + + /* + * do_parent() returns EXIT_SUCCESS if the child was killed by + * SIGSYS (i.e. seccomp correctly saw the modified argument), + * and EXIT_FAILURE if the child exited normally (bypass). + */ + int result = do_parent(child); + + ksft_test_result(result == EXIT_SUCCESS, "seccomp_ptrace_x0_bypass\n"); + + ksft_print_cnts(); + return result; +} diff --git a/tools/testing/selftests/arm64/abi/seccomp_ret_trace_x0_bypass.c b/tools/testing/selftests/arm64/abi/seccomp_ret_trace_x0_bypass.c new file mode 100644 index 000000000000..a23081763328 --- /dev/null +++ b/tools/testing/selftests/arm64/abi/seccomp_ret_trace_x0_bypass.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test for SECCOMP_RET_TRACE argument modification bypass + * via stale orig_x0 during filter re-evaluation. + * + * On arm64, syscall_get_arguments() reads the first argument from + * regs->orig_x0. When a seccomp filter returns SECCOMP_RET_TRACE, + * ptrace may modify regs->regs[0] while orig_x0 remains unchanged. + * The kernel then re-evaluates the filter; if it sees the stale + * orig_x0, it may incorrectly allow a syscall that the tracer intended + * to block. + * + * This test installs a filter that: + * - TRACEs write() when fd == 2 + * - returns ERRNO(EPERM) when fd == 1 + * - allows all other syscalls + * + * The child calls write(2, ...). The parent catches the SECCOMP stop, + * changes x0 (fd) from 2 to 1, and resumes the child. + * + * If re-evaluation sees the old fd=2 (stale orig_x0), the filter + * returns TRACE again; because recheck_after_trace is true, the kernel + * allows the syscall to proceed. write(1, ...) succeeds, child exits 0. + * -> test FAIL (bypass detected). + * + * If re-evaluation sees the new fd=1 (synced orig_x0), the filter + * returns ERRNO(EPERM), write fails, child exits 1. + * -> test PASS (no bypass). + * + * No special privileges required beyond CAP_SYS_PTRACE. + */ +#include <errno.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/prctl.h> +#include <sys/ptrace.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <linux/elf.h> +#include <linux/filter.h> +#include <linux/seccomp.h> +#include <linux/ptrace.h> +#include <asm/unistd.h> + +#include "kselftest.h" + +#define PTRACE_EVENT_MASK(status) ((status) >> 16) + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define ARG0_OFFSET (offsetof(struct seccomp_data, args)) +#else +#define ARG0_OFFSET (offsetof(struct seccomp_data, args) + 4) +#endif + +static int do_child(void) +{ + long ret; + + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) + _exit(2); + + raise(SIGSTOP); /* synchronize with parent */ + + /* + * Filter: + * if syscall == write: + * if fd == 2 -> TRACE + * if fd == 1 -> ERRNO(EPERM) + * else -> ALLOW + * else -> ALLOW + */ + struct sock_filter filter[] = { + /* Load syscall number */ + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)), + /* If not write, allow */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 5), + /* Load first argument (fd) */ + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, ARG0_OFFSET), + /* fd == 2 ? */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 2, 0, 1), + /* Yes: TRACE */ + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE), + /* fd == 1 ? */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1), + /* Yes: ERRNO(EPERM) */ + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & SECCOMP_RET_DATA)), + /* Other fd: ALLOW */ + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + }; + + struct sock_fprog prog = { + .len = ARRAY_SIZE(filter), + .filter = filter, + }; + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + _exit(3); + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) + _exit(4); + + /* + * write(2, ...) triggers TRACE, parent changes fd to 1. + * If re-eval sees fd=1 -> ERRNO -> write fails, ret = -EPERM. + * If re-eval sees fd=2 -> TRACE again -> allowed -> write succeeds. + */ + ret = syscall(__NR_write, 2, "", 0); + _exit(ret == 0 ? 0 : 1); +} + +int main(void) +{ + struct user_pt_regs regs; + struct iovec iov = { .iov_base = ®s, .iov_len = sizeof(regs) }; + pid_t child; + int status; + + ksft_print_header(); + ksft_set_plan(1); + + child = fork(); + if (child < 0) + ksft_exit_fail_msg("fork failed: %s", strerror(errno)); + + if (!child) + return do_child(); + + /* 1. Wait for initial SIGSTOP */ + if (waitpid(child, &status, 0) != child) + ksft_exit_fail_msg("waitpid SIGSTOP"); + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) + ksft_exit_fail_msg("unexpected initial stop"); + + /* 2. Enable SECCOMP ptrace events */ + if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESECCOMP)) + ksft_exit_fail_msg("PTRACE_SETOPTIONS"); + + /* 3. Continue child to hit SECCOMP stop */ + if (ptrace(PTRACE_CONT, child, 0, 0)) + ksft_exit_fail_msg("PTRACE_CONT"); + + /* 4. Wait for SECCOMP stop */ + while (1) { + if (waitpid(child, &status, 0) != child) + ksft_exit_fail_msg("waitpid SECCOMP"); + if (WIFEXITED(status)) { + ksft_test_result_fail("child exited before SECCOMP stop\n"); + goto out; + } + if (WIFSIGNALED(status)) { + ksft_test_result_fail("child killed unexpectedly\n"); + goto out; + } + if (WIFSTOPPED(status) && + WSTOPSIG(status) == SIGTRAP && + PTRACE_EVENT_MASK(status) == PTRACE_EVENT_SECCOMP) + break; + ptrace(PTRACE_CONT, child, 0, WSTOPSIG(status)); + } + + /* 5. Modify x0 (fd) from 2 to 1 */ + if (ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &iov)) + ksft_exit_fail_perror("GETREGSET"); + if (regs.regs[8] != __NR_write || regs.regs[0] != 2) { + ksft_test_result_fail("unexpected regs: syscall=%llu, x0=%llu\n", + regs.regs[8], regs.regs[0]); + goto out; + } + regs.regs[0] = 1; + if (ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &iov)) + ksft_exit_fail_perror("SETREGSET"); + + /* 6. Resume child */ + if (ptrace(PTRACE_CONT, child, 0, 0)) + ksft_exit_fail_perror("PTRACE_CONT"); + + /* 7. Reap child – must exit normally */ + if (waitpid(child, &status, 0) != child) + ksft_exit_fail_msg("final waitpid"); + + if (!WIFEXITED(status)) { + ksft_test_result_fail("child did not exit normally\n"); + goto out; + } + + if (WEXITSTATUS(status) != 0) + ksft_test_result_pass("seccomp correctly denied modified syscall\n"); + else + ksft_test_result_fail("write succeeded, orig_x0 bypass likely\n"); + +out: + if (child > 0) { + kill(child, SIGKILL); + waitpid(child, NULL, 0); + } + ksft_print_cnts(); + return ksft_get_fail_cnt() ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/arm64/fp/fp-ptrace.c b/tools/testing/selftests/arm64/fp/fp-ptrace.c index 22c584b78be5..b435837c8c0e 100644 --- a/tools/testing/selftests/arm64/fp/fp-ptrace.c +++ b/tools/testing/selftests/arm64/fp/fp-ptrace.c @@ -65,6 +65,9 @@ /* VL 128..2048 in powers of 2 */ #define MAX_NUM_VLS 5 +/* Sentinel for detecting buffer bytes the kernel did not write */ +#define REGSET_SENTINEL 0xa5 + /* * FPMR bits we can set without doing feature checks to see if values * are valid. @@ -181,6 +184,20 @@ static bool compare_buffer(const char *name, void *out, return false; } +static bool buffer_is_filled(const void *buffer, size_t size, + unsigned char value) +{ + const unsigned char *bytes = buffer; + size_t i; + + for (i = 0; i < size; i++) { + if (bytes[i] != value) + return false; + } + + return true; +} + struct test_config { int sve_vl_in; int sve_vl_expected; @@ -401,6 +418,7 @@ static bool check_ptrace_values_sve(pid_t child, struct test_config *config) struct user_sve_header *sve; struct user_fpsimd_state *fpsimd; struct iovec iov; + size_t buf_size; int ret, vq; bool pass = true; @@ -409,14 +427,16 @@ static bool check_ptrace_values_sve(pid_t child, struct test_config *config) vq = __sve_vq_from_vl(config->sve_vl_in); - iov.iov_len = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE); - iov.iov_base = malloc(iov.iov_len); + buf_size = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE); + iov.iov_len = buf_size; + iov.iov_base = malloc(buf_size); if (!iov.iov_base) { ksft_print_msg("OOM allocating %lu byte SVE buffer\n", iov.iov_len); return false; } + memset(iov.iov_base, REGSET_SENTINEL, buf_size); ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_SVE, &iov); if (ret != 0) { ksft_print_msg("Failed to read initial SVE: %s (%d)\n", @@ -440,10 +460,16 @@ static bool check_ptrace_values_sve(pid_t child, struct test_config *config) } if (svcr_in & SVCR_SM) { - if (sve->size != sizeof(sve)) { + if (sve->size != sizeof(*sve)) { ksft_print_msg("NT_ARM_SVE reports data with PSTATE.SM\n"); pass = false; } + if (!buffer_is_filled(iov.iov_base + sizeof(*sve), + buf_size - sizeof(*sve), REGSET_SENTINEL)) { + ksft_print_msg("NT_ARM_SVE wrote beyond its header with PSTATE.SM\n"); + pass = false; + } + goto out; } else { if (sve->size != SVE_PT_SIZE(vq, sve->flags)) { ksft_print_msg("Mismatch in SVE header size: %d != %lu\n", @@ -485,6 +511,7 @@ static bool check_ptrace_values_ssve(pid_t child, struct test_config *config) struct user_sve_header *sve; struct user_fpsimd_state *fpsimd; struct iovec iov; + size_t buf_size; int ret, vq; bool pass = true; @@ -493,14 +520,16 @@ static bool check_ptrace_values_ssve(pid_t child, struct test_config *config) vq = __sve_vq_from_vl(config->sme_vl_in); - iov.iov_len = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE); - iov.iov_base = malloc(iov.iov_len); + buf_size = SVE_PT_SVE_OFFSET + SVE_PT_SVE_SIZE(vq, SVE_PT_REGS_SVE); + iov.iov_len = buf_size; + iov.iov_base = malloc(buf_size); if (!iov.iov_base) { ksft_print_msg("OOM allocating %lu byte SSVE buffer\n", iov.iov_len); return false; } + memset(iov.iov_base, REGSET_SENTINEL, buf_size); ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_SSVE, &iov); if (ret != 0) { ksft_print_msg("Failed to read initial SSVE: %s (%d)\n", @@ -523,10 +552,16 @@ static bool check_ptrace_values_ssve(pid_t child, struct test_config *config) } if (!(svcr_in & SVCR_SM)) { - if (sve->size != sizeof(sve)) { + if (sve->size != sizeof(*sve)) { ksft_print_msg("NT_ARM_SSVE reports data without PSTATE.SM\n"); pass = false; } + if (!buffer_is_filled(iov.iov_base + sizeof(*sve), + buf_size - sizeof(*sve), REGSET_SENTINEL)) { + ksft_print_msg("NT_ARM_SSVE wrote beyond its header without PSTATE.SM\n"); + pass = false; + } + goto out; } else { if (sve->size != SVE_PT_SIZE(vq, sve->flags)) { ksft_print_msg("Mismatch in SSVE header size: %d != %lu\n", diff --git a/tools/testing/selftests/arm64/fp/sve-test.S b/tools/testing/selftests/arm64/fp/sve-test.S index 80e072f221cd..7ef7835389e7 100644 --- a/tools/testing/selftests/arm64/fp/sve-test.S +++ b/tools/testing/selftests/arm64/fp/sve-test.S @@ -298,15 +298,20 @@ function irritator_handler add x0, x0, #1 str x0, [x2, #ucontext_regs + 8 * 23] +#ifndef SSVE // Corrupt some random Z-regs movi v0.8b, #1 movi v9.16b, #2 movi v31.8b, #3 // And P0 ptrue p0.d -#ifndef SSVE // And FFR wrffr p15.b +#else + // Enter and exit streaming mode, will reset all of the V, Z, P + // and FFR registers that the system has. + smstart_sm + smstop #endif ret diff --git a/tools/testing/selftests/arm64/gcs/libc-gcs.c b/tools/testing/selftests/arm64/gcs/libc-gcs.c index 72e82bfbecc9..ddb0b2b20155 100644 --- a/tools/testing/selftests/arm64/gcs/libc-gcs.c +++ b/tools/testing/selftests/arm64/gcs/libc-gcs.c @@ -130,7 +130,7 @@ TEST(gcs_find_terminator) * We can access a GCS via ptrace * * This could usefully have a fixture but note that each test is - * fork()ed into a new child whcih causes issues. Might be better to + * fork()ed into a new child which causes issues. Might be better to * lift at least some of this out into a separate, non-harness, test * program. */ diff --git a/tools/testing/selftests/arm64/pauth/pac.c b/tools/testing/selftests/arm64/pauth/pac.c index 67d138057707..f4b859c75a5a 100644 --- a/tools/testing/selftests/arm64/pauth/pac.c +++ b/tools/testing/selftests/arm64/pauth/pac.c @@ -292,7 +292,7 @@ TEST(single_thread_different_keys) /* * fork() does not change keys. Only exec() does so call a worker program. - * Its only job is to sign a value and report back the resutls + * Its only job is to sign a value and report back the results */ TEST(exec_changed_keys) { diff --git a/tools/testing/selftests/bpf/bench.c b/tools/testing/selftests/bpf/bench.c index 3d9d2cd7764b..c4a3a6b3eb83 100644 --- a/tools/testing/selftests/bpf/bench.c +++ b/tools/testing/selftests/bpf/bench.c @@ -539,12 +539,12 @@ extern const struct bench bench_trig_uretprobe_multi_push; extern const struct bench bench_trig_uprobe_multi_ret; extern const struct bench bench_trig_uretprobe_multi_ret; #ifdef __x86_64__ -extern const struct bench bench_trig_uprobe_nop5; -extern const struct bench bench_trig_uretprobe_nop5; -extern const struct bench bench_trig_uprobe_multi_nop5; -extern const struct bench bench_trig_uretprobe_multi_nop5; +extern const struct bench bench_trig_uprobe_nop10; +extern const struct bench bench_trig_uretprobe_nop10; +extern const struct bench bench_trig_uprobe_multi_nop10; +extern const struct bench bench_trig_uretprobe_multi_nop10; extern const struct bench bench_trig_usdt_nop; -extern const struct bench bench_trig_usdt_nop5; +extern const struct bench bench_trig_usdt_nop10; #endif extern const struct bench bench_rb_libbpf; @@ -622,12 +622,12 @@ static const struct bench *benchs[] = { &bench_trig_uprobe_multi_ret, &bench_trig_uretprobe_multi_ret, #ifdef __x86_64__ - &bench_trig_uprobe_nop5, - &bench_trig_uretprobe_nop5, - &bench_trig_uprobe_multi_nop5, - &bench_trig_uretprobe_multi_nop5, + &bench_trig_uprobe_nop10, + &bench_trig_uretprobe_nop10, + &bench_trig_uprobe_multi_nop10, + &bench_trig_uretprobe_multi_nop10, &bench_trig_usdt_nop, - &bench_trig_usdt_nop5, + &bench_trig_usdt_nop10, #endif /* ringbuf/perfbuf benchmarks */ &bench_rb_libbpf, diff --git a/tools/testing/selftests/bpf/benchs/bench_trigger.c b/tools/testing/selftests/bpf/benchs/bench_trigger.c index 2f22ec61667b..61513efc167a 100644 --- a/tools/testing/selftests/bpf/benchs/bench_trigger.c +++ b/tools/testing/selftests/bpf/benchs/bench_trigger.c @@ -396,15 +396,15 @@ static void *uprobe_producer_ret(void *input) } #ifdef __x86_64__ -__nocf_check __weak void uprobe_target_nop5(void) +__nocf_check __weak void uprobe_target_nop10(void) { - asm volatile (".byte 0x0f, 0x1f, 0x44, 0x00, 0x00"); + asm volatile (".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00"); } -static void *uprobe_producer_nop5(void *input) +static void *uprobe_producer_nop10(void *input) { while (true) - uprobe_target_nop5(); + uprobe_target_nop10(); return NULL; } @@ -418,7 +418,7 @@ static void *uprobe_producer_usdt_nop(void *input) return NULL; } -static void *uprobe_producer_usdt_nop5(void *input) +static void *uprobe_producer_usdt_nop10(void *input) { while (true) usdt_2(); @@ -542,24 +542,24 @@ static void uretprobe_multi_ret_setup(void) } #ifdef __x86_64__ -static void uprobe_nop5_setup(void) +static void uprobe_nop10_setup(void) { - usetup(false, false /* !use_multi */, &uprobe_target_nop5); + usetup(false, false /* !use_multi */, &uprobe_target_nop10); } -static void uretprobe_nop5_setup(void) +static void uretprobe_nop10_setup(void) { - usetup(true, false /* !use_multi */, &uprobe_target_nop5); + usetup(true, false /* !use_multi */, &uprobe_target_nop10); } -static void uprobe_multi_nop5_setup(void) +static void uprobe_multi_nop10_setup(void) { - usetup(false, true /* use_multi */, &uprobe_target_nop5); + usetup(false, true /* use_multi */, &uprobe_target_nop10); } -static void uretprobe_multi_nop5_setup(void) +static void uretprobe_multi_nop10_setup(void) { - usetup(true, true /* use_multi */, &uprobe_target_nop5); + usetup(true, true /* use_multi */, &uprobe_target_nop10); } static void usdt_setup(const char *name) @@ -598,7 +598,7 @@ static void usdt_nop_setup(void) usdt_setup("usdt_1"); } -static void usdt_nop5_setup(void) +static void usdt_nop10_setup(void) { usdt_setup("usdt_2"); } @@ -665,10 +665,10 @@ BENCH_TRIG_USERMODE(uretprobe_multi_nop, nop, "uretprobe-multi-nop"); BENCH_TRIG_USERMODE(uretprobe_multi_push, push, "uretprobe-multi-push"); BENCH_TRIG_USERMODE(uretprobe_multi_ret, ret, "uretprobe-multi-ret"); #ifdef __x86_64__ -BENCH_TRIG_USERMODE(uprobe_nop5, nop5, "uprobe-nop5"); -BENCH_TRIG_USERMODE(uretprobe_nop5, nop5, "uretprobe-nop5"); -BENCH_TRIG_USERMODE(uprobe_multi_nop5, nop5, "uprobe-multi-nop5"); -BENCH_TRIG_USERMODE(uretprobe_multi_nop5, nop5, "uretprobe-multi-nop5"); +BENCH_TRIG_USERMODE(uprobe_nop10, nop10, "uprobe-nop10"); +BENCH_TRIG_USERMODE(uretprobe_nop10, nop10, "uretprobe-nop10"); +BENCH_TRIG_USERMODE(uprobe_multi_nop10, nop10, "uprobe-multi-nop10"); +BENCH_TRIG_USERMODE(uretprobe_multi_nop10, nop10, "uretprobe-multi-nop10"); BENCH_TRIG_USERMODE(usdt_nop, usdt_nop, "usdt-nop"); -BENCH_TRIG_USERMODE(usdt_nop5, usdt_nop5, "usdt-nop5"); +BENCH_TRIG_USERMODE(usdt_nop10, usdt_nop10, "usdt-nop10"); #endif diff --git a/tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh b/tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh index 9ec59423b949..e490b337e960 100755 --- a/tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh +++ b/tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh @@ -2,7 +2,7 @@ set -eufo pipefail -for i in usermode-count syscall-count {uprobe,uretprobe}-{nop,push,ret,nop5} usdt-nop usdt-nop5 +for i in usermode-count syscall-count {uprobe,uretprobe}-{nop,push,ret,nop10} usdt-nop usdt-nop10 do summary=$(sudo ./bench -w2 -d5 -a trig-$i | tail -n1 | cut -d'(' -f1 | cut -d' ' -f3-) printf "%-15s: %s\n" $i "$summary" diff --git a/tools/testing/selftests/bpf/bpf_experimental.h b/tools/testing/selftests/bpf/bpf_experimental.h index 67ff7882299e..f30a4ac939db 100644 --- a/tools/testing/selftests/bpf/bpf_experimental.h +++ b/tools/testing/selftests/bpf/bpf_experimental.h @@ -364,19 +364,25 @@ extern void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it) __weak __ksym; extern int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__str, struct bpf_dynptr *value_p) __weak __ksym; +extern int bpf_sock_read_xattr(struct socket *sock, const char *name__str, + struct bpf_dynptr *value_p) __weak __ksym; + #define PREEMPT_BITS 8 #define SOFTIRQ_BITS 8 +#define HARDIRQ_DISABLE_BITS 8 #define HARDIRQ_BITS 4 -#define NMI_BITS 4 +#define NMI_BITS 1 #define PREEMPT_SHIFT 0 #define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS) -#define HARDIRQ_SHIFT (SOFTIRQ_SHIFT + SOFTIRQ_BITS) +#define HARDIRQ_DISABLE_SHIFT (SOFTIRQ_SHIFT + SOFTIRQ_BITS) +#define HARDIRQ_SHIFT (HARDIRQ_DISABLE_SHIFT + HARDIRQ_DISABLE_BITS) #define NMI_SHIFT (HARDIRQ_SHIFT + HARDIRQ_BITS) #define __IRQ_MASK(x) ((1UL << (x))-1) #define SOFTIRQ_MASK (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT) +#define HARDIRQ_DISABLE_MASK (__IRQ_MASK(HARDIRQ_DISABLE_BITS) << HARDIRQ_DISABLE_SHIFT) #define HARDIRQ_MASK (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT) #define NMI_MASK (__IRQ_MASK(NMI_BITS) << NMI_SHIFT) diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config index adb25146e88c..ea7044f30adc 100644 --- a/tools/testing/selftests/bpf/config +++ b/tools/testing/selftests/bpf/config @@ -82,6 +82,7 @@ CONFIG_NET_SCH_BPF=y CONFIG_NET_SCH_FQ=y CONFIG_NET_SCH_INGRESS=y CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_RED=y CONFIG_NET_SCHED=y CONFIG_NETDEVSIM=y CONFIG_NETFILTER=y diff --git a/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c b/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c new file mode 100644 index 000000000000..61cc5aaba025 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/kfunc_implicit_args_tracing.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include <test_progs.h> +#include "kfunc_implicit_args_tracing.skel.h" + +void test_kfunc_implicit_args_tracing(void) +{ + struct kfunc_implicit_args_tracing *skel; + LIBBPF_OPTS(bpf_test_run_opts, topts); + int err, fd; + + skel = kfunc_implicit_args_tracing__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + err = kfunc_implicit_args_tracing__attach(skel); + if (!ASSERT_OK(err, "attach")) + goto cleanup; + + fd = bpf_program__fd(skel->progs.trigger_implicit_arg); + err = bpf_prog_test_run_opts(fd, &topts); + if (!ASSERT_OK(err, "test_run")) + goto cleanup; + + ASSERT_EQ(topts.retval, 5, "kfunc_retval"); + ASSERT_EQ(skel->bss->fentry_arg_cnt, 2, "fentry_arg_cnt"); + ASSERT_NEQ(skel->bss->fentry_aux_arg, 0, "fentry_aux_arg"); + ASSERT_EQ(skel->bss->fentry_result, 1, "fentry_result"); + ASSERT_EQ(skel->bss->fexit_arg_cnt, 2, "fexit_arg_cnt"); + ASSERT_NEQ(skel->bss->fexit_aux_arg, 0, "fexit_aux_arg"); + ASSERT_EQ(skel->bss->fexit_result, 1, "fexit_result"); + +cleanup: + kfunc_implicit_args_tracing__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c new file mode 100644 index 000000000000..b8538fc4fc3f --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_bad_access.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <test_progs.h> +#include "test_kmods/bpf_testmod.h" +#include "bpf_util.h" + +static void check_attach_reject(const struct bpf_insn *program, size_t prog_len) +{ + LIBBPF_OPTS(bpf_prog_load_opts, opts); + char error[4096]; + int bpf_fd, tp_fd; + + opts.log_level = 2; + opts.log_buf = error; + opts.log_size = sizeof(error); + + bpf_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, NULL, "GPL v2", + program, prog_len, &opts); + if (!ASSERT_GE(bpf_fd, 0, "prog_load")) + return; + + tp_fd = bpf_raw_tracepoint_open("bpf_testmod_test_writable_bare_tp", bpf_fd); + ASSERT_EQ(tp_fd, -EINVAL, "bpf_raw_tracepoint_open"); + if (tp_fd >= 0) + close(tp_fd); + + close(bpf_fd); +} + +void test_raw_tp_writable_reject_bad_access(void) +{ + const struct bpf_insn program[] = { + /* r6 is our tp buffer */ + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0), + /* one byte beyond the end of the writable context */ + BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, + sizeof(struct bpf_testmod_test_writable_ctx)), + BPF_EXIT_INSN(), + }; + + const struct bpf_insn negative_var_off_program[] = { + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0), + /* make var_off negative, but keep the effective access offset non-negative */ + BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, -8), + /* one byte beyond the end of the writable context */ + BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, + sizeof(struct bpf_testmod_test_writable_ctx) + 8), + BPF_EXIT_INSN(), + }; + + if (test__start_subtest("past_end")) + check_attach_reject(program, ARRAY_SIZE(program)); + + if (test__start_subtest("negative_var_off_past_end")) + check_attach_reject(negative_var_off_program, + ARRAY_SIZE(negative_var_off_program)); +} diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c deleted file mode 100644 index 216b0dfac0fe..000000000000 --- a/tools/testing/selftests/bpf/prog_tests/raw_tp_writable_reject_nbd_invalid.c +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -#include <test_progs.h> -#include <linux/nbd.h> -#include "bpf_util.h" - -void test_raw_tp_writable_reject_nbd_invalid(void) -{ - __u32 duration = 0; - char error[4096]; - int bpf_fd = -1, tp_fd = -1; - - const struct bpf_insn program[] = { - /* r6 is our tp buffer */ - BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, 0), - /* one byte beyond the end of the nbd_request struct */ - BPF_LDX_MEM(BPF_B, BPF_REG_0, BPF_REG_6, - sizeof(struct nbd_request)), - BPF_EXIT_INSN(), - }; - - LIBBPF_OPTS(bpf_prog_load_opts, opts, - .log_level = 2, - .log_buf = error, - .log_size = sizeof(error), - ); - - bpf_fd = bpf_prog_load(BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, NULL, "GPL v2", - program, ARRAY_SIZE(program), - &opts); - if (CHECK(bpf_fd < 0, "bpf_raw_tracepoint_writable load", - "failed: %d errno %d\n", bpf_fd, errno)) - return; - - tp_fd = bpf_raw_tracepoint_open("nbd_send_request", bpf_fd); - if (CHECK(tp_fd >= 0, "bpf_raw_tracepoint_writable open", - "erroneously succeeded\n")) - goto out_bpffd; - - close(tp_fd); -out_bpffd: - close(bpf_fd); -} diff --git a/tools/testing/selftests/bpf/prog_tests/sock_xattr.c b/tools/testing/selftests/bpf/prog_tests/sock_xattr.c new file mode 100644 index 000000000000..b5816e90f01a --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/sock_xattr.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2026 Christian Brauner */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/xattr.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <test_progs.h> + +#include "sock_read_xattr.skel.h" + +static const char xattr_value[] = "bpf_sock_value"; +static const char xattr_name[] = "user.bpf_test"; + +static void test_read_sock_xattr(void) +{ + struct sockaddr_in addr = {}; + struct sock_read_xattr *skel = NULL; + struct bpf_link *link = NULL; + int sock_fd = -1, err; + + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + if (!ASSERT_OK_FD(sock_fd, "socket")) + return; + + err = fsetxattr(sock_fd, xattr_name, xattr_value, sizeof(xattr_value), 0); + if (!ASSERT_OK(err, "fsetxattr")) + goto out; + + skel = sock_read_xattr__open_and_load(); + if (!ASSERT_OK_PTR(skel, "sock_read_xattr__open_and_load")) + goto out; + + skel->bss->monitored_pid = sys_gettid(); + + /* Only attach the functional program; the verifier-only programs + * above are not pid-gated and would clobber the shared globals. + */ + link = bpf_program__attach(skel->progs.read_sock_xattr); + if (!ASSERT_OK_PTR(link, "attach read_sock_xattr")) + goto out; + + addr.sin_family = AF_INET; + addr.sin_port = htons(1234); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + /* Only the lsm/socket_connect hook matters; the connect may fail. */ + connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr)); + + ASSERT_EQ(skel->data->read_ret, sizeof(xattr_value), "read_ret"); + ASSERT_STREQ(skel->bss->value, xattr_value, "value"); + +out: + bpf_link__destroy(link); + if (sock_fd >= 0) + close(sock_fd); + sock_read_xattr__destroy(skel); +} + +void test_sock_xattr(void) +{ + RUN_TESTS(sock_read_xattr); + + if (test__start_subtest("read_sock_xattr")) + test_read_sock_xattr(); +} diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c index cb3229711f93..e5fc038d747b 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c @@ -853,7 +853,7 @@ static void test_sockmap_many_socket(void) return; } - udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK); if (udp < 0) { close(dgram); close(tcp); @@ -922,7 +922,7 @@ static void test_sockmap_many_maps(void) return; } - udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK); if (udp < 0) { close(dgram); close(tcp); @@ -993,7 +993,7 @@ static void test_sockmap_same_sock(void) return; } - udp = xsocket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + udp = socket_loopback(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK); if (udp < 0) { close(dgram); close(tcp); @@ -1373,6 +1373,43 @@ end: test_sockmap_pass_prog__destroy(skel); } +/* A socket in a sockmap without a verdict program keeps its ingress data + * in sk_receive_queue: FIONREAD must account for it. + */ +static void test_sockmap_no_verdict_fionread(void) +{ + int err, map, zero = 0, sent, avail; + int c0 = -1, c1 = -1, p0 = -1, p1 = -1; + struct test_sockmap_pass_prog *skel; + char buf[256] = "0123456789"; + + skel = test_sockmap_pass_prog__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + map = bpf_map__fd(skel->maps.sock_map_rx); + + err = create_socket_pairs(AF_INET, SOCK_STREAM, &c0, &c1, &p0, &p1); + if (!ASSERT_OK(err, "create_socket_pairs()")) + goto out; + + err = bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST); + if (!ASSERT_OK(err, "bpf_map_update_elem(c1)")) + goto out_close; + + sent = xsend(p1, &buf, sizeof(buf), 0); + ASSERT_EQ(sent, sizeof(buf), "xsend(p1)"); + avail = wait_for_fionread(c1, sizeof(buf), IO_TIMEOUT_SEC); + ASSERT_EQ(avail, sizeof(buf), "ioctl(FIONREAD)"); + +out_close: + close(c0); + close(p0); + close(c1); + close(p1); +out: + test_sockmap_pass_prog__destroy(skel); +} + void test_sockmap_basic(void) { if (test__start_subtest("sockmap create_update_free")) @@ -1415,6 +1452,8 @@ void test_sockmap_basic(void) test_sockmap_skb_verdict_shutdown(); if (test__start_subtest("sockmap skb_verdict fionread")) test_sockmap_skb_verdict_fionread(true); + if (test__start_subtest("sockmap no_verdict fionread")) + test_sockmap_no_verdict_fionread(); if (test__start_subtest("sockmap skb_verdict fionread on drop")) test_sockmap_skb_verdict_fionread(false); if (test__start_subtest("sockmap skb_verdict change tail")) diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c index cc0c68bab907..1c96a3cf4b97 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_listen.c @@ -53,8 +53,8 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused, int family, int sotype, int mapfd) { u32 key = 0; - u64 value; int err, s; + u64 value; s = xsocket(family, sotype, 0); if (s == -1) @@ -63,11 +63,8 @@ static void test_insert_opened(struct test_sockmap_listen *skel __always_unused, errno = 0; value = s; err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); - if (sotype == SOCK_STREAM) { - if (!err || errno != EOPNOTSUPP) - FAIL_ERRNO("map_update: expected EOPNOTSUPP"); - } else if (err) - FAIL_ERRNO("map_update: expected success"); + ASSERT_ERR(err, "map_update"); + ASSERT_EQ(errno, EOPNOTSUPP, "errno"); xclose(s); } @@ -77,8 +74,8 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused, struct sockaddr_storage addr; socklen_t len = 0; u32 key = 0; - u64 value; int err, s; + u64 value; init_addr_loopback(family, &addr, &len); @@ -93,8 +90,12 @@ static void test_insert_bound(struct test_sockmap_listen *skel __always_unused, errno = 0; value = s; err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); - if (!err || errno != EOPNOTSUPP) - FAIL_ERRNO("map_update: expected EOPNOTSUPP"); + if (sotype == SOCK_STREAM) { + ASSERT_ERR(err, "map_update"); + ASSERT_EQ(errno, EOPNOTSUPP, "errno"); + } else { + ASSERT_OK(err, "map_update"); + } close: xclose(s); } @@ -1289,7 +1290,7 @@ static void test_ops(struct test_sockmap_listen *skel, struct bpf_map *map, /* insert */ TEST(test_insert_invalid), TEST(test_insert_opened), - TEST(test_insert_bound, SOCK_STREAM), + TEST(test_insert_bound), TEST(test_insert), /* delete */ TEST(test_delete_after_insert), diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c b/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c index 621b3b71888e..1d7231728eaf 100644 --- a/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c +++ b/tools/testing/selftests/bpf/prog_tests/sockmap_strp.c @@ -431,6 +431,35 @@ out: test_sockmap_strp__destroy(strp); } +static void test_sockmap_strp_parser_reject(void) +{ + struct test_sockmap_strp *strp = NULL; + int parser_mod, parser_ro, link; + int err, map; + + strp = test_sockmap_strp__open_and_load(); + if (!ASSERT_OK_PTR(strp, "test_sockmap_strp__open_and_load")) + return; + + map = bpf_map__fd(strp->maps.sock_map); + parser_mod = bpf_program__fd(strp->progs.prog_skb_parser_resize); + parser_ro = bpf_program__fd(strp->progs.prog_skb_parser); + + err = bpf_prog_attach(parser_mod, map, BPF_SK_SKB_STREAM_PARSER, 0); + ASSERT_ERR(err, "bpf_prog_attach parser_mod"); + + link = bpf_link_create(parser_ro, map, BPF_SK_SKB_STREAM_PARSER, NULL); + if (!ASSERT_GE(link, 0, "bpf_link_create parser_ro")) + goto out; + + err = bpf_link_update(link, parser_mod, NULL); + ASSERT_ERR(err, "bpf_link_update parser_mod"); +out: + if (link >= 0) + close(link); + test_sockmap_strp__destroy(strp); +} + void test_sockmap_strp(void) { if (test__start_subtest("sockmap strp tcp pass")) @@ -451,4 +480,6 @@ void test_sockmap_strp(void) test_sockmap_strp_multiple_pkt(AF_INET, SOCK_STREAM); if (test__start_subtest("sockmap strp tcp dispatch")) test_sockmap_strp_dispatch_pkt(AF_INET, SOCK_STREAM); + if (test__start_subtest("sockmap strp parser reject pkt mod")) + test_sockmap_strp_parser_reject(); } diff --git a/tools/testing/selftests/bpf/prog_tests/tc_qevent.c b/tools/testing/selftests/bpf/prog_tests/tc_qevent.c new file mode 100644 index 000000000000..67e1d17567ab --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tc_qevent.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <test_progs.h> +#include <network_helpers.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <string.h> + +#include "test_tc_qevent.skel.h" + +#define NS_TX "tc_qevent_tx" +#define NS_RX "tc_qevent_rx" +#define IP_TX "10.255.0.1" +#define IP_RX "10.255.0.2" +#define PIN_PATH "/sys/fs/bpf/tc_qevent_redirect" + +static void blast_udp(void) +{ + struct sockaddr_in dst = {}; + char buf[1400] = {}; + int fd, i; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (!ASSERT_GE(fd, 0, "udp socket")) + return; + + dst.sin_family = AF_INET; + dst.sin_port = htons(12345); + inet_pton(AF_INET, IP_RX, &dst.sin_addr); + + /* + * Push far more than the RED queue can hold. Once qavg crosses qth_min + * every further packet hits the congestion_drop / early_drop qevent. + */ + for (i = 0; i < 50000; i++) + sendto(fd, buf, sizeof(buf), MSG_DONTWAIT, + (struct sockaddr *)&dst, sizeof(dst)); + + close(fd); +} + +static void run_qevent_redirect(struct bpf_program *prog, __u64 *counter) +{ + struct nstoken *tok = NULL; + int err; + + SYS_NOFAIL("ip netns del %s", NS_TX); + SYS_NOFAIL("ip netns del %s", NS_RX); + unlink(PIN_PATH); + + err = bpf_program__pin(prog, PIN_PATH); + if (!ASSERT_OK(err, "pin prog")) + return; + + SYS(unpin, "ip netns add %s", NS_TX); + SYS(del_tx, "ip netns add %s", NS_RX); + SYS(del_rx, "ip -n %s link add veth0 type veth peer name veth1 netns %s", NS_TX, NS_RX); + SYS(del_rx, "ip -n %s addr add %s/24 dev veth0", NS_TX, IP_TX); + SYS(del_rx, "ip -n %s link set veth0 up", NS_TX); + SYS(del_rx, "ip -n %s addr add %s/24 dev veth1", NS_RX, IP_RX); + SYS(del_rx, "ip -n %s link set veth1 up", NS_RX); + + tok = open_netns(NS_TX); + if (!ASSERT_OK_PTR(tok, "open_netns")) + goto del_rx; + + SYS(close_ns, "tc qdisc add dev veth0 root handle 1: htb default 1"); + SYS(close_ns, "tc class add dev veth0 parent 1: classid 1:1 htb rate 1mbit ceil 1mbit"); + + if (system("tc qdisc add dev veth0 parent 1:1 handle 11: red " + "limit 500000 avpkt 1000 probability 1 min 5000 max 6000 " + "burst 6 qevent early_drop block 10 2>/dev/null")) { + test__skip(); + goto close_ns; + } + + if (system("tc filter add block 10 bpf da object-pinned " + PIN_PATH " 2>/dev/null")) { + test__skip(); + goto close_ns; + } + + blast_udp(); + ASSERT_GT(*counter, 0, "qevent classifier ran"); +close_ns: + close_netns(tok); +del_rx: + SYS_NOFAIL("ip netns del %s", NS_RX); +del_tx: + SYS_NOFAIL("ip netns del %s", NS_TX); +unpin: + bpf_program__unpin(prog, PIN_PATH); +} + +void test_tc_qevent(void) +{ + struct test_tc_qevent *skel; + + skel = test_tc_qevent__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + if (test__start_subtest("redirect_verdict")) + run_qevent_redirect(skel->progs.qevent_redirect_verdict, + &skel->bss->verdict_calls); + if (test__start_subtest("redirect_helper")) + run_qevent_redirect(skel->progs.qevent_redirect_helper, + &skel->bss->helper_calls); + + test_tc_qevent__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 6eb9096d084c..38ce6060b8fa 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -427,14 +427,14 @@ static u32 pkt_nb_frags(u32 frame_size, struct pkt_stream *pkt_stream, struct pk } /* Search for the end of the packet in verbatim mode */ - if (!pkt_continues(pkt->options) || !pkt->valid) + if (!pkt_continues(pkt->options)) return nb_frags; next_frag = pkt_stream->current_pkt_nb; pkt++; while (next_frag++ < pkt_stream->nb_pkts) { nb_frags++; - if (!pkt_continues(pkt->options) || !pkt->valid) + if (!pkt_continues(pkt->options)) break; pkt++; } @@ -665,11 +665,11 @@ static struct pkt_stream *__pkt_stream_generate_custom(struct ifobject *ifobj, s if (!frame->valid || !pkt_continues(frame->options)) payload++; } else { - if (frame->valid) + if (frame->valid) { len += frame->len; - if (frame->valid && pkt_continues(frame->options)) - continue; - + if (pkt_continues(frame->options)) + continue; + } pkt->pkt_nb = pkt_nb; pkt->len = len; pkt->valid = frame->valid; @@ -1250,10 +1250,9 @@ static int __send_pkts(struct ifobject *ifobject, struct xsk_socket_info *xsk, } } - if (pkt && pkt->valid) { + if (pkt && pkt->valid) valid_pkts++; - valid_frags += nb_frags; - } + valid_frags += nb_frags; } pthread_mutex_lock(&pacing_mutex); @@ -2099,13 +2098,16 @@ int testapp_invalid_desc_mb(struct test_spec *test) {0, 0, 0, false, 0}, /* Invalid address in the second frame */ {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, - {umem_sz, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {umem_sz * 2, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {0, MIN_PKT_SIZE, 0, false, 0}, /* Invalid len in the middle */ {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, {0, XSK_UMEM__INVALID_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, + {0, MIN_PKT_SIZE, 0, false, 0}, /* Invalid options in the middle */ {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XDP_PKT_CONTD}, {0, XSK_UMEM__LARGE_FRAME_SIZE, 0, false, XSK_DESC__INVALID_OPTION}, + {0, MIN_PKT_SIZE, 0, false, 0}, /* Transmit 2 frags, receive 3 */ {0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, XDP_PKT_CONTD}, {0, XSK_UMEM__MAX_FRAME_SIZE, 0, true, 0}, @@ -2117,8 +2119,8 @@ int testapp_invalid_desc_mb(struct test_spec *test) if (umem->unaligned_mode) { /* Crossing a chunk boundary allowed */ - pkts[12].valid = true; - pkts[13].valid = true; + pkts[15].valid = true; + pkts[16].valid = true; } test->mtu = MAX_ETH_JUMBO_SIZE; @@ -2270,7 +2272,7 @@ int testapp_too_many_frags(struct test_spec *test) max_frags += 1; } - pkts = calloc(2 * max_frags + 2, sizeof(struct pkt)); + pkts = calloc(2 * max_frags + 3, sizeof(struct pkt)); if (!pkts) return TEST_FAILURE; @@ -2288,24 +2290,30 @@ int testapp_too_many_frags(struct test_spec *test) } pkts[max_frags].options = 0; - /* An invalid packet with the max amount of frags but signals packet - * continues on the last frag - */ - for (i = max_frags + 1; i < 2 * max_frags + 1; i++) { + /* An invalid packet with the max + 1 amount of frags */ + for (i = max_frags + 1; i < 2 * max_frags + 2; i++) { pkts[i].len = MIN_PKT_SIZE; pkts[i].options = XDP_PKT_CONTD; - pkts[i].valid = false; + pkts[i].valid = true; } + pkts[2 * max_frags + 1].options = 0; /* Valid packet for synch */ - pkts[2 * max_frags + 1].len = MIN_PKT_SIZE; - pkts[2 * max_frags + 1].valid = true; + pkts[2 * max_frags + 2].len = MIN_PKT_SIZE; + pkts[2 * max_frags + 2].valid = true; - if (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 2)) { + if (pkt_stream_generate_custom(test, pkts, 2 * max_frags + 3)) { free(pkts); return TEST_FAILURE; } + /* The generated Tx stream must keep the too-big packet valid so that + * __send_pkts() accounts its descriptors in outstanding_tx. The Rx + * stream, however, must not expect this packet on the wire. + */ + test->ifobj_rx->xsk->pkt_stream->pkts[2].valid = false; + test->ifobj_rx->xsk->pkt_stream->nb_valid_entries--; + ret = testapp_validate_traffic(test); free(pkts); return ret; diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c index 955a37751b52..e193206fc5d2 100644 --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c @@ -4,6 +4,8 @@ #ifdef __x86_64__ +#define _GNU_SOURCE +#include <sched.h> #include <unistd.h> #include <asm/ptrace.h> #include <linux/compiler.h> @@ -13,11 +15,12 @@ #include <sys/syscall.h> #include <sys/prctl.h> #include <asm/prctl.h> +#include <stdnoreturn.h> #include "uprobe_syscall.skel.h" #include "uprobe_syscall_executed.skel.h" #include "bpf/libbpf_internal.h" -#define USDT_NOP .byte 0x0f, 0x1f, 0x44, 0x00, 0x00 +#define USDT_NOP .byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 #include "usdt.h" #pragma GCC diagnostic ignored "-Wattributes" @@ -26,7 +29,7 @@ __attribute__((aligned(16))) __nocf_check __weak __naked unsigned long uprobe_regs_trigger(void) { asm volatile ( - ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00\n" /* nop5 */ + ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10 */ "movq $0xdeadbeef, %rax\n" "ret\n" ); @@ -345,9 +348,9 @@ cleanup: __attribute__((aligned(16))) __nocf_check __weak __naked void uprobe_test(void) { - asm volatile (" \n" - ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00 \n" - "ret \n" + asm volatile ( + ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10 */ + "ret\n" ); } @@ -357,6 +360,50 @@ __nocf_check __weak void usdt_test(void) USDT(optimized_uprobe, usdt); } +/* + * Assembly-level red zone clobbering test. Stores known values in the + * red zone (below RSP), executes a nop10 (uprobe site), and checks that + * the values survived. Returns 0 if intact, 1 if clobbered. + * + * The nop5 optimization used CALL (which pushes a return address to + * [rsp-8]), the value at -8(%rsp) was overwritten. The nop10 optimization + * should escape that by moving stackpointer below the redzone before + * doing the CALL. + * + * Align the code at 64 bytes, to make sure nop10 is not on page boundary. + */ +__attribute__((aligned(64))) +__nocf_check __weak __naked unsigned long uprobe_red_zone_test(void) +{ + asm volatile ( + "movabs $0x1111111111111111, %%rax\n" + "movq %%rax, -8(%%rsp)\n" + "movabs $0x2222222222222222, %%rax\n" + "movq %%rax, -16(%%rsp)\n" + "movabs $0x3333333333333333, %%rax\n" + "movq %%rax, -24(%%rsp)\n" + + ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10: uprobe site */ + + "movabs $0x1111111111111111, %%rax\n" + "cmpq %%rax, -8(%%rsp)\n" + "jne 1f\n" + "movabs $0x2222222222222222, %%rax\n" + "cmpq %%rax, -16(%%rsp)\n" + "jne 1f\n" + "movabs $0x3333333333333333, %%rax\n" + "cmpq %%rax, -24(%%rsp)\n" + "jne 1f\n" + + "xorl %%eax, %%eax\n" + "retq\n" + "1:\n" + "movl $1, %%eax\n" + "retq\n" + ::: "rax", "memory" + ); +} + static int find_uprobes_trampoline(void *tramp_addr) { void *start, *end; @@ -388,14 +435,15 @@ static int find_uprobes_trampoline(void *tramp_addr) return ret; } -static unsigned char nop5[5] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; +static unsigned char nop10[10] = { 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static unsigned char lea_rsp[5] = { 0x48, 0x8d, 0x64, 0x24, 0x80 }; -static void *find_nop5(void *fn) +static void *find_nop10(void *fn) { int i; - for (i = 0; i < 10; i++) { - if (!memcmp(nop5, fn + i, 5)) + for (i = 0; i < 128; i++) { + if (!memcmp(nop10, fn + i, 10)) return fn + i; } return NULL; @@ -403,6 +451,16 @@ static void *find_nop5(void *fn) typedef void (__attribute__((nocf_check)) *trigger_t)(void); +static void check_attach_notrigger(struct uprobe_syscall_executed *skel, + void *addr, int executed) +{ + unsigned char *op = addr; + + /* Make sure bpf program was not executed. */ + ASSERT_EQ(skel->bss->executed, executed, "executed"); + ASSERT_EQ(*op, 0xcc, "int3"); +} + static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigger, void *addr, int executed) { @@ -420,7 +478,8 @@ static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigge ASSERT_EQ(skel->bss->executed, executed, "executed"); /* .. and check the trampoline is as expected. */ - call = (struct __arch_relative_insn *) addr; + ASSERT_OK(memcmp(addr, lea_rsp, 5), "lea_rsp"); + call = (struct __arch_relative_insn *)(addr + 5); tramp = (void *) (call + 1) + call->raddr; ASSERT_EQ(call->op, 0xe8, "call"); ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline"); @@ -428,21 +487,26 @@ static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigge return tramp; } -static void check_detach(void *addr, void *tramp) +static bool check_detach(void *addr, void *tramp) { + static const unsigned char nop10_prefix[] = { 0x66, 0x2e, 0x0f, 0x1f, 0x84 }; + bool ok = true; + /* [uprobes_trampoline] stays after detach */ - ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline"); - ASSERT_OK(memcmp(addr, nop5, 5), "nop5"); + ok &= ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline"); + ok &= ASSERT_OK(memcmp(addr, nop10_prefix, 5), "nop10_prefix"); + return ok; } -static void check(struct uprobe_syscall_executed *skel, struct bpf_link *link, - trigger_t trigger, void *addr, int executed) +static void *check(struct uprobe_syscall_executed *skel, struct bpf_link *link, + trigger_t trigger, void *addr, int executed) { void *tramp; tramp = check_attach(skel, trigger, addr, executed); bpf_link__destroy(link); check_detach(addr, tramp); + return tramp; } static void test_uprobe_legacy(void) @@ -453,6 +517,7 @@ static void test_uprobe_legacy(void) ); struct bpf_link *link; unsigned long offset; + void *tramp; offset = get_uprobe_offset(&uprobe_test); if (!ASSERT_GE(offset, 0, "get_uprobe_offset")) @@ -470,7 +535,30 @@ static void test_uprobe_legacy(void) if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts")) goto cleanup; - check(skel, link, uprobe_test, uprobe_test, 2); + tramp = check(skel, link, uprobe_test, uprobe_test, 2); + + /* reattach and detach without triggering optimization */ + link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe, + 0, "/proc/self/exe", offset, NULL); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts")) + goto cleanup; + + check_attach_notrigger(skel, uprobe_test, 2); + + bpf_link__destroy(link); + if (!check_detach(uprobe_test, tramp)) + goto cleanup; + + uprobe_test(); + ASSERT_EQ(skel->bss->executed, 2, "executed_no_probe"); + + /* reattach with triggering optimization */ + link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe, + 0, "/proc/self/exe", offset, NULL); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts")) + goto cleanup; + + check(skel, link, uprobe_test, uprobe_test, 4); /* uretprobe */ skel->bss->executed = 0; @@ -492,6 +580,7 @@ static void test_uprobe_multi(void) LIBBPF_OPTS(bpf_uprobe_multi_opts, opts); struct bpf_link *link; unsigned long offset; + void *tramp; offset = get_uprobe_offset(&uprobe_test); if (!ASSERT_GE(offset, 0, "get_uprobe_offset")) @@ -512,7 +601,30 @@ static void test_uprobe_multi(void) if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) goto cleanup; - check(skel, link, uprobe_test, uprobe_test, 2); + tramp = check(skel, link, uprobe_test, uprobe_test, 2); + + /* reattach and detach without triggering optimization */ + link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_multi, + 0, "/proc/self/exe", NULL, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) + goto cleanup; + + check_attach_notrigger(skel, uprobe_test, 2); + + bpf_link__destroy(link); + if (!check_detach(uprobe_test, tramp)) + goto cleanup; + + uprobe_test(); + ASSERT_EQ(skel->bss->executed, 2, "executed_no_probe"); + + /* reattach with triggering optimization */ + link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_multi, + 0, "/proc/self/exe", NULL, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) + goto cleanup; + + check(skel, link, uprobe_test, uprobe_test, 4); /* uretprobe.multi */ skel->bss->executed = 0; @@ -536,6 +648,7 @@ static void test_uprobe_session(void) ); struct bpf_link *link; unsigned long offset; + void *tramp; offset = get_uprobe_offset(&uprobe_test); if (!ASSERT_GE(offset, 0, "get_uprobe_offset")) @@ -555,7 +668,30 @@ static void test_uprobe_session(void) if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) goto cleanup; - check(skel, link, uprobe_test, uprobe_test, 4); + tramp = check(skel, link, uprobe_test, uprobe_test, 4); + + /* reattach and detach without triggering optimization */ + link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_session, + 0, "/proc/self/exe", NULL, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) + goto cleanup; + + check_attach_notrigger(skel, uprobe_test, 4); + + bpf_link__destroy(link); + if (!check_detach(uprobe_test, tramp)) + goto cleanup; + + uprobe_test(); + ASSERT_EQ(skel->bss->executed, 4, "executed_no_probe"); + + /* reattach with triggering optimization */ + link = bpf_program__attach_uprobe_multi(skel->progs.test_uprobe_session, + 0, "/proc/self/exe", NULL, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_multi")) + goto cleanup; + + check(skel, link, uprobe_test, uprobe_test, 8); cleanup: uprobe_syscall_executed__destroy(skel); @@ -565,11 +701,11 @@ static void test_uprobe_usdt(void) { struct uprobe_syscall_executed *skel; struct bpf_link *link; - void *addr; + void *addr, *tramp; errno = 0; - addr = find_nop5(usdt_test); - if (!ASSERT_OK_PTR(addr, "find_nop5")) + addr = find_nop10(usdt_test); + if (!ASSERT_OK_PTR(addr, "find_nop10")) return; skel = uprobe_syscall_executed__open_and_load(); @@ -584,7 +720,32 @@ static void test_uprobe_usdt(void) if (!ASSERT_OK_PTR(link, "bpf_program__attach_usdt")) goto cleanup; - check(skel, link, usdt_test, addr, 2); + tramp = check(skel, link, usdt_test, addr, 2); + + /* reattach and detach without triggering optimization */ + link = bpf_program__attach_usdt(skel->progs.test_usdt, + -1 /* all PIDs */, "/proc/self/exe", + "optimized_uprobe", "usdt", NULL); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_usdt")) + goto cleanup; + + check_attach_notrigger(skel, addr, 2); + + bpf_link__destroy(link); + if (!check_detach(addr, tramp)) + goto cleanup; + + usdt_test(); + ASSERT_EQ(skel->bss->executed, 2, "executed_no_probe"); + + /* reattach with triggering optimization */ + link = bpf_program__attach_usdt(skel->progs.test_usdt, + -1 /* all PIDs */, "/proc/self/exe", + "optimized_uprobe", "usdt", NULL); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_usdt")) + goto cleanup; + + check(skel, link, usdt_test, addr, 4); cleanup: uprobe_syscall_executed__destroy(skel); @@ -757,12 +918,124 @@ cleanup: #define __NR_uprobe 336 #endif +static void test_uprobe_red_zone(void) +{ + struct uprobe_syscall_executed *skel; + struct bpf_link *link; + void *nop10_addr; + size_t offset; + int i; + + nop10_addr = find_nop10(uprobe_red_zone_test); + if (!ASSERT_NEQ(nop10_addr, NULL, "find_nop10")) + return; + + skel = uprobe_syscall_executed__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + offset = get_uprobe_offset(nop10_addr); + link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe, + 0, "/proc/self/exe", offset, NULL); + if (!ASSERT_OK_PTR(link, "attach_uprobe")) + goto cleanup; + + for (i = 0; i < 10; i++) + ASSERT_EQ(uprobe_red_zone_test(), 0, "red_zone_intact"); + + bpf_link__destroy(link); + +cleanup: + uprobe_syscall_executed__destroy(skel); +} + static void test_uprobe_error(void) { long err = syscall(__NR_uprobe); ASSERT_EQ(err, -1, "error"); - ASSERT_EQ(errno, ENXIO, "errno"); + ASSERT_EQ(errno, EPROTO, "errno"); +} + +__attribute__((aligned(16))) +__nocf_check __weak __naked void uprobe_fork_test(void) +{ + asm volatile ( + ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10 */ + "ret\n" + ); +} + +static noreturn int child_func(void *arg) +{ + struct uprobe_syscall_executed *skel = arg; + + /* Make sure the child's probe is still there and optimized.. */ + if (memcmp(uprobe_fork_test, lea_rsp, sizeof(lea_rsp))) + _exit(1); + + skel->bss->pid = getpid(); + + /* .. and it executes properly. */ + uprobe_fork_test(); + + if (skel->bss->executed != 3) + _exit(2); + + _exit(0); +} + +static void test_uprobe_fork_optimized(bool clone_vm) +{ + struct uprobe_syscall_executed *skel = NULL; + unsigned long offset; + int pid, status, err; + char stack[65535]; + + offset = get_uprobe_offset(&uprobe_fork_test); + if (!ASSERT_GE(offset, 0, "get_uprobe_offset")) + return; + + skel = uprobe_syscall_executed__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + goto cleanup; + + skel->links.test_uprobe = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe, + -1, "/proc/self/exe", offset, NULL); + if (!ASSERT_OK_PTR(skel->links.test_uprobe, "attach_uprobe")) + goto cleanup; + + skel->bss->pid = getpid(); + + /* Trigger optimization of uprobe in uprobe_fork_test. */ + uprobe_fork_test(); + uprobe_fork_test(); + + /* Make sure it got optimied. */ + if (!ASSERT_OK(memcmp(uprobe_fork_test, lea_rsp, sizeof(lea_rsp)), "optimized")) + goto cleanup; + + if (clone_vm) { + pid = clone(child_func, stack + sizeof(stack), CLONE_VM|SIGCHLD, skel); + if (!ASSERT_GT(pid, 0, "clone")) + goto cleanup; + } else { + pid = fork(); + if (!ASSERT_GE(pid, 0, "fork")) + goto cleanup; + if (pid == 0) + child_func(skel); + } + + /* Wait for the child and verify it exited properly with 0. */ + err = waitpid(pid, &status, 0); + if (ASSERT_EQ(err, pid, "waitpid")) { + ASSERT_EQ(WIFEXITED(status), 1, "child_exited"); + ASSERT_EQ(WEXITSTATUS(status), 0, "child_exit_code"); + } + +cleanup: + uprobe_syscall_executed__destroy(skel); } static void __test_uprobe_syscall(void) @@ -783,6 +1056,12 @@ static void __test_uprobe_syscall(void) test_uprobe_usdt(); if (test__start_subtest("uprobe_race")) test_uprobe_race(); + if (test__start_subtest("uprobe_red_zone")) + test_uprobe_red_zone(); + if (test__start_subtest("uprobe_optimized_fork")) + test_uprobe_fork_optimized(false); + if (test__start_subtest("uprobe_optimized_clone_vm")) + test_uprobe_fork_optimized(true); if (test__start_subtest("uprobe_error")) test_uprobe_error(); if (test__start_subtest("uprobe_regs_equal")) diff --git a/tools/testing/selftests/bpf/prog_tests/usdt.c b/tools/testing/selftests/bpf/prog_tests/usdt.c index 69759b27794d..8004c9568ffa 100644 --- a/tools/testing/selftests/bpf/prog_tests/usdt.c +++ b/tools/testing/selftests/bpf/prog_tests/usdt.c @@ -250,9 +250,10 @@ cleanup: #ifdef __x86_64__ extern void usdt_1(void); extern void usdt_2(void); +extern void usdt_red_zone_trigger(void); static unsigned char nop1[1] = { 0x90 }; -static unsigned char nop1_nop5_combo[6] = { 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x00 }; +static unsigned char nop1_nop10_combo[11] = { 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }; static void *find_instr(void *fn, unsigned char *instr, size_t cnt) { @@ -271,17 +272,17 @@ static void subtest_optimized_attach(void) __u8 *addr_1, *addr_2; /* usdt_1 USDT probe has single nop instruction */ - addr_1 = find_instr(usdt_1, nop1_nop5_combo, 6); - if (!ASSERT_NULL(addr_1, "usdt_1_find_nop1_nop5_combo")) + addr_1 = find_instr(usdt_1, nop1_nop10_combo, 11); + if (!ASSERT_NULL(addr_1, "usdt_1_find_nop1_nop10_combo")) return; addr_1 = find_instr(usdt_1, nop1, 1); if (!ASSERT_OK_PTR(addr_1, "usdt_1_find_nop1")) return; - /* usdt_2 USDT probe has nop,nop5 instructions combo */ - addr_2 = find_instr(usdt_2, nop1_nop5_combo, 6); - if (!ASSERT_OK_PTR(addr_2, "usdt_2_find_nop1_nop5_combo")) + /* usdt_2 USDT probe has nop,nop10 instructions combo */ + addr_2 = find_instr(usdt_2, nop1_nop10_combo, 11); + if (!ASSERT_OK_PTR(addr_2, "usdt_2_find_nop1_nop10_combo")) return; skel = test_usdt__open_and_load(); @@ -309,12 +310,12 @@ static void subtest_optimized_attach(void) bpf_link__destroy(skel->links.usdt_executed); - /* we expect the nop5 ip */ + /* we expect the nop10 ip */ skel->bss->expected_ip = (unsigned long) addr_2 + 1; /* * Attach program on top of usdt_2 which is probe defined on top - * of nop1,nop5 combo, so the probe gets optimized on top of nop5. + * of nop1,nop10 combo, so the probe gets optimized on top of nop10. */ skel->links.usdt_executed = bpf_program__attach_usdt(skel->progs.usdt_executed, 0 /*self*/, "/proc/self/exe", @@ -328,13 +329,64 @@ static void subtest_optimized_attach(void) /* nop stays on addr_2 address */ ASSERT_EQ(*addr_2, 0x90, "nop"); - /* call is on addr_2 + 1 address */ - ASSERT_EQ(*(addr_2 + 1), 0xe8, "call"); + /* + * lea -0x80(%rsp), %rsp + * call ... + */ + static unsigned char expected[] = { 0x48, 0x8d, 0x64, 0x24, 0x80, 0xe8 }; + + ASSERT_MEMEQ(addr_2 + 1, expected, sizeof(expected), "lea_and_call"); ASSERT_EQ(skel->bss->executed, 4, "executed"); cleanup: test_usdt__destroy(skel); } + +/* + * Test that USDT arguments survive nop10 optimization in a function where + * the compiler places operands in the red zone. + * + * Signal handlers are prone to having the compiler place USDT argument + * operands in the red zone (below rsp). + * + * The nop5 optimization used CALL (which pushes a return address to + * [rsp-8]), the value at -8(%rsp) was overwritten. The nop10 optimization + * should escape that by moving stackpointer below the redzone before + * doing the CALL. + */ +static void subtest_optimized_red_zone(void) +{ + struct test_usdt *skel; + int i; + + skel = test_usdt__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open_and_load")) + return; + + skel->bss->expected_arg[0] = 0xDEADBEEF; + skel->bss->expected_arg[1] = 0xCAFEBABE; + skel->bss->expected_arg[2] = 0xFEEDFACE; + skel->bss->expected_pid = getpid(); + + skel->links.usdt_check_arg = bpf_program__attach_usdt( + skel->progs.usdt_check_arg, 0, "/proc/self/exe", + "optimized_attach", "usdt_red_zone", NULL); + if (!ASSERT_OK_PTR(skel->links.usdt_check_arg, "attach_usdt_red_zone")) + goto cleanup; + + for (i = 0; i < 10; i++) + usdt_red_zone_trigger(); + + ASSERT_EQ(skel->bss->arg_total, 10, "arg_total"); + ASSERT_EQ(skel->bss->arg_bad, 0, "arg_bad"); + ASSERT_EQ(skel->bss->arg_last[0], 0xDEADBEEF, "arg_last_1"); + ASSERT_EQ(skel->bss->arg_last[1], 0xCAFEBABE, "arg_last_2"); + ASSERT_EQ(skel->bss->arg_last[2], 0xFEEDFACE, "arg_last_3"); + +cleanup: + test_usdt__destroy(skel); +} + #endif unsigned short test_usdt_100_semaphore SEC(".probes"); @@ -608,6 +660,8 @@ void test_usdt(void) subtest_basic_usdt(true); if (test__start_subtest("optimized_attach")) subtest_optimized_attach(); + if (test__start_subtest("optimized_red_zone")) + subtest_optimized_red_zone(); #endif if (test__start_subtest("multispec")) subtest_multispec_usdt(); diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c index 8a3d69e2453c..be97f6887f0e 100644 --- a/tools/testing/selftests/bpf/prog_tests/verifier.c +++ b/tools/testing/selftests/bpf/prog_tests/verifier.c @@ -78,6 +78,7 @@ #include "verifier_precision.skel.h" #include "verifier_prevent_map_lookup.skel.h" #include "verifier_private_stack.skel.h" +#include "verifier_ptr_to_buf.skel.h" #include "verifier_raw_stack.skel.h" #include "verifier_raw_tp_writable.skel.h" #include "verifier_reg_equal.skel.h" @@ -230,6 +231,7 @@ void test_verifier_or_jmp32_k(void) { RUN(verifier_or_jmp32_k); } void test_verifier_precision(void) { RUN(verifier_precision); } void test_verifier_prevent_map_lookup(void) { RUN(verifier_prevent_map_lookup); } void test_verifier_private_stack(void) { RUN(verifier_private_stack); } +void test_verifier_ptr_to_buf(void) { RUN(verifier_ptr_to_buf); } void test_verifier_raw_stack(void) { RUN(verifier_raw_stack); } void test_verifier_raw_tp_writable(void) { RUN(verifier_raw_tp_writable); } void test_verifier_reg_equal(void) { RUN(verifier_reg_equal); } diff --git a/tools/testing/selftests/bpf/progs/dynptr_fail.c b/tools/testing/selftests/bpf/progs/dynptr_fail.c index 344fb2aa0813..29c6361d8820 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_fail.c +++ b/tools/testing/selftests/bpf/progs/dynptr_fail.c @@ -1635,6 +1635,36 @@ static int callback(__u32 index, void *data) return 0; } +/* A commuted add should preserve the parent id of a dynptr data slice. */ +SEC("?raw_tp") +__failure __msg("invalid mem access 'scalar'") +int dynptr_slice_commuted_invalidate(void *ctx) +{ + struct bpf_dynptr ptr; + __u32 *slice, *derived; + + bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(__u32), 0, &ptr); + + slice = bpf_dynptr_data(&ptr, 0, sizeof(__u32)); + if (!slice) + goto done; + + asm volatile ("%[dst] = 0;" + "%[dst] += %[src];" + "%[src] = 0;" + : [dst]"=&r"(derived), [src]"+r"(slice) + : + : "memory"); + + bpf_ringbuf_discard_dynptr(&ptr, 0); + val = *derived; + return 0; + +done: + bpf_ringbuf_discard_dynptr(&ptr, 0); + return 0; +} + /* If the dynptr is written into in a callback function, its data * slices should be invalidated as well. */ diff --git a/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c b/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c new file mode 100644 index 000000000000..995f8b8b5b9e --- /dev/null +++ b/tools/testing/selftests/bpf/progs/kfunc_implicit_args_tracing.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ + +#include <vmlinux.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> +#include <errno.h> + +extern int bpf_kfunc_implicit_arg(int a) __weak __ksym; + +char _license[] SEC("license") = "GPL"; + +/* Shared arg checks; reports arg count and aux, returns 1 on success. */ +static __always_inline __u64 +check_implicit_args(void *ctx, __u64 *arg_cnt, __u64 *aux_arg) +{ + __u64 a = 0, aux = 0, z = 0; + __u64 result; + __s64 err; + + *arg_cnt = bpf_get_func_arg_cnt(ctx); + result = *arg_cnt == 2; + + err = bpf_get_func_arg(ctx, 0, &a); + result &= err == 0 && (int)a == 5; + + err = bpf_get_func_arg(ctx, 1, &aux); + *aux_arg = aux; + result &= err == 0 && aux != 0; + + err = bpf_get_func_arg(ctx, 2, &z); + result &= err == -EINVAL; + + return result; +} + +__u64 fentry_result; +__u64 fentry_arg_cnt; +__u64 fentry_aux_arg; + +SEC("fentry/bpf_kfunc_implicit_arg") +int BPF_PROG(trace_implicit_arg_fentry) +{ + __u64 ret = 0; + __s64 err; + + fentry_result = check_implicit_args(ctx, &fentry_arg_cnt, &fentry_aux_arg); + + err = bpf_get_func_ret(ctx, &ret); + fentry_result &= err == -EOPNOTSUPP; + + return 0; +} + +__u64 fexit_result; +__u64 fexit_arg_cnt; +__u64 fexit_aux_arg; + +SEC("fexit/bpf_kfunc_implicit_arg") +int BPF_PROG(trace_implicit_arg_fexit) +{ + __u64 ret = 0; + __s64 err; + + fexit_result = check_implicit_args(ctx, &fexit_arg_cnt, &fexit_aux_arg); + + err = bpf_get_func_ret(ctx, &ret); + fexit_result &= err == 0 && ret == 5; + + return 0; +} + +SEC("syscall") +int trigger_implicit_arg(void *ctx) +{ + return bpf_kfunc_implicit_arg(5); +} diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c index 5b4453747c23..f166fff8f217 100644 --- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c +++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c @@ -226,4 +226,21 @@ int null_check(void *ctx) return 0; } +SEC("socket") +__success +__retval(1) +int ldx_is_ok_commuted_addr(void *ctx) +{ + int v, *p, *derived; + + v = 1; + p = bpf_rdonly_cast(&v, 0); + asm volatile ("%[dst] = 0;" + "%[dst] += %[src];" + : [dst]"=&r"(derived) + : [src]"r"(p) + : "memory"); + return *derived; +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c index 7247a20c0a3b..024ef2aae200 100644 --- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c +++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c @@ -13,12 +13,20 @@ struct node_acquire { struct bpf_refcount refcount; }; +struct node_refcounted { + long key; + struct bpf_list_node list; + struct bpf_refcount refcount; +}; + extern void bpf_rcu_read_lock(void) __ksym; extern void bpf_rcu_read_unlock(void) __ksym; #define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8))) private(A) struct bpf_spin_lock glock; private(A) struct bpf_rb_root groot __contains(node_acquire, node); +private(B) struct bpf_spin_lock lock; +private(B) struct bpf_list_head head __contains(node_refcounted, list); static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b) { @@ -93,6 +101,32 @@ long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx) return 0; } +SEC("?tc") +__failure __msg("dereference of modified ptr_ ptr R1") +long refcount_acquire_list_node_offset(void *ctx) +{ + struct node_refcounted *node, *base, *ref; + struct bpf_list_node *list_node; + + node = bpf_obj_new(typeof(*node)); + if (!node) + return 1; + + bpf_spin_lock(&lock); + bpf_list_push_front(&head, &node->list); + list_node = bpf_list_pop_front(&head); + bpf_spin_unlock(&lock); + if (!list_node) + return 2; + + base = container_of(list_node, struct node_refcounted, list); + ref = bpf_refcount_acquire(list_node); + if (ref) + bpf_obj_drop(ref); + bpf_obj_drop(base); + return 0; +} + SEC("?fentry.s/" SYS_PREFIX "sys_getpgid") __failure __msg("function calls are not allowed while holding a lock") int BPF_PROG(rbtree_fail_sleepable_lock_across_rcu, diff --git a/tools/testing/selftests/bpf/progs/sock_read_xattr.c b/tools/testing/selftests/bpf/progs/sock_read_xattr.c new file mode 100644 index 000000000000..c4a8eae8cc3c --- /dev/null +++ b/tools/testing/selftests/bpf/progs/sock_read_xattr.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Christian Brauner */ + +#include <vmlinux.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_core_read.h> +#include "bpf_experimental.h" +#include "bpf_misc.h" + +char _license[] SEC("license") = "GPL"; + +char value[16]; +int read_ret = -1; +__u32 monitored_pid = 0; + +static __always_inline void read_xattr(struct socket *sock) +{ + struct bpf_dynptr value_ptr; + + bpf_dynptr_from_mem(value, sizeof(value), 0, &value_ptr); + bpf_sock_read_xattr(sock, "user.bpf_test", &value_ptr); +} + +SEC("lsm.s/socket_connect") +__success +int BPF_PROG(trusted_sock_ptr_sleepable, struct socket *sock) +{ + read_xattr(sock); + return 0; +} + +SEC("lsm/socket_connect") +__success +int BPF_PROG(trusted_sock_ptr_non_sleepable, struct socket *sock) +{ + read_xattr(sock); + return 0; +} + +SEC("lsm.s/socket_connect") +__success +int BPF_PROG(read_sock_xattr, struct socket *sock) +{ + struct bpf_dynptr value_ptr; + __u32 pid = bpf_get_current_pid_tgid() >> 32; + + if (pid != monitored_pid) + return 0; + + bpf_dynptr_from_mem(value, sizeof(value), 0, &value_ptr); + read_ret = bpf_sock_read_xattr(sock, "user.bpf_test", &value_ptr); + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c b/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c index c9abfe3a11af..56e9aebf05f2 100644 --- a/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c +++ b/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c @@ -5,28 +5,6 @@ SEC("sk_skb1") int bpf_prog1(struct __sk_buff *skb) { - void *data_end = (void *)(long) skb->data_end; - void *data = (void *)(long) skb->data; - __u8 *d = data; - int err; - - if (data + 10 > data_end) { - err = bpf_skb_pull_data(skb, 10); - if (err) - return SK_DROP; - - data_end = (void *)(long)skb->data_end; - data = (void *)(long)skb->data; - if (data + 10 > data_end) - return SK_DROP; - } - - /* This write/read is a bit pointless but tests the verifier and - * strparser handler for read/write pkt data and access into sk - * fields. - */ - d = data; - d[7] = 1; return skb->len; } diff --git a/tools/testing/selftests/bpf/progs/test_sockmap_strp.c b/tools/testing/selftests/bpf/progs/test_sockmap_strp.c index dde3d5bec515..fe88fa6d40bc 100644 --- a/tools/testing/selftests/bpf/progs/test_sockmap_strp.c +++ b/tools/testing/selftests/bpf/progs/test_sockmap_strp.c @@ -50,4 +50,11 @@ int prog_skb_parser_partial(struct __sk_buff *skb) return 10; } +SEC("sk_skb/stream_parser") +int prog_skb_parser_resize(struct __sk_buff *skb) +{ + bpf_skb_change_tail(skb, skb->len, 0); + return skb->len; +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/test_tc_qevent.c b/tools/testing/selftests/bpf/progs/test_tc_qevent.c new file mode 100644 index 000000000000..1529c111f4aa --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_tc_qevent.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> + +int redirect_ifindex = 1; +__u64 verdict_calls = 0; +__u64 helper_calls = 0; + +SEC("tc") +int qevent_redirect_verdict(struct __sk_buff *skb) +{ + __sync_fetch_and_add(&verdict_calls, 1); + return TCX_REDIRECT; +} + +SEC("tc") +int qevent_redirect_helper(struct __sk_buff *skb) +{ + __sync_fetch_and_add(&helper_calls, 1); + return bpf_redirect(redirect_ifindex, 0); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/test_usdt.c b/tools/testing/selftests/bpf/progs/test_usdt.c index f00cb52874e0..0ee78fb050a1 100644 --- a/tools/testing/selftests/bpf/progs/test_usdt.c +++ b/tools/testing/selftests/bpf/progs/test_usdt.c @@ -149,5 +149,30 @@ int usdt_executed(struct pt_regs *ctx) executed++; return 0; } + +int arg_total; +int arg_bad; +long arg_last[3]; +long expected_arg[3]; +int expected_pid; + +SEC("usdt") +int BPF_USDT(usdt_check_arg, long arg1, long arg2, long arg3) +{ + if (expected_pid != (bpf_get_current_pid_tgid() >> 32)) + return 0; + + __sync_fetch_and_add(&arg_total, 1); + arg_last[0] = arg1; + arg_last[1] = arg2; + arg_last[2] = arg3; + + if (arg1 != expected_arg[0] || + arg2 != expected_arg[1] || + arg3 != expected_arg[2]) + __sync_fetch_and_add(&arg_bad, 1); + + return 0; +} #endif char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_basic_stack.c b/tools/testing/selftests/bpf/progs/verifier_basic_stack.c index fb62e09f2114..d3df7a9f1d8c 100644 --- a/tools/testing/selftests/bpf/progs/verifier_basic_stack.c +++ b/tools/testing/selftests/bpf/progs/verifier_basic_stack.c @@ -97,4 +97,45 @@ __naked void misaligned_read_from_stack(void) " ::: __clobber_all); } +SEC("socket") +__description("stack pointer arithmetic preserves frame number") +__failure __msg("R7 invalid mem access 'scalar'") +__naked void stack_ptr_arith_preserves_frameno(void) +{ + asm volatile ("\ + r3 = 0; \ + *(u64 *)(r10 - 8) = r3; \ + r1 = %[map_hash_8b] ll; \ + r2 = r10; \ + r2 += -8; \ + call %[bpf_map_lookup_elem]; \ + if r0 != 0 goto +2; \ + r0 = 0; \ + exit; \ + r1 = r0; \ + r2 = 0; \ + r3 = 0; \ + call stack_ptr_arith_preserves_frameno_subprog;\ + r0 = 0; \ + exit; \ + ": + : __imm(bpf_map_lookup_elem), + __imm_addr(map_hash_8b) + : __clobber_all); +} + +static __used __naked void stack_ptr_arith_preserves_frameno_subprog(void) +{ + asm volatile ("\ + *(u64 *)(r10 - 8) = r1; \ + r6 = -8; \ + r6 += r10; \ + *(u64 *)(r6 + 0) = r2; \ + r7 = *(u64 *)(r10 - 8); \ + *(u64 *)(r7 + 0) = r3; \ + r0 = 0; \ + exit; \ + "::: __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c index 75a2e3f48d0f..67dc352addfd 100644 --- a/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c +++ b/tools/testing/selftests/bpf/progs/verifier_global_subprogs.c @@ -185,6 +185,16 @@ int arg_tag_nonnull_ptr_good(void *ctx) return subprog_nonnull_ptr_good(&x, &y); } +SEC("?raw_tp") +__failure __log_level(2) +__msg("R1 is expected to be non-NULL") +int arg_tag_nonnull_ptr_null_bad(void *ctx) +{ + int y = 74; + + return subprog_nonnull_ptr_good(NULL, &y); +} + /* this global subprog can be now called from many types of entry progs, each * with different context type */ diff --git a/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c new file mode 100644 index 000000000000..12cf24db46a0 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_ptr_to_buf.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <vmlinux.h> +#include <bpf/bpf_helpers.h> +#include "bpf_misc.h" + +SEC("iter/bpf_map_elem") +__description("PTR_TO_BUF: reject negative const offset") +__failure +__msg("invalid negative rdwr buffer offset") +__naked void ptr_to_buf_reject_negative_const_offset(void) +{ + asm volatile ("r0 = 0; \ + r2 = *(u64 *)(r1 + %[value_off]); \ + if r2 == 0 goto l0_%=; \ + r2 += -8; \ + r0 = *(u64 *)(r2 + 0); \ +l0_%=: \ + exit; \ + " + : + : __imm_const(value_off, + offsetof(struct bpf_iter__bpf_map_elem, value)) + : __clobber_all); +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c index 14a0172e2141..4055a6443bc2 100644 --- a/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c +++ b/tools/testing/selftests/bpf/progs/verifier_raw_tp_writable.c @@ -47,4 +47,20 @@ l0_%=: /* shift the buffer pointer to a variable location */\ : __clobber_all); } +SEC("raw_tracepoint.w") +__description("raw_tracepoint_writable: reject negative const offset") +__failure +__msg("invalid negative tracepoint buffer offset") +__naked void tracepoint_writable_reject_negative_const_offset(void) +{ + asm volatile (" \ + r6 = *(u64 *)(r1 + 0); \ + r6 += -8; \ + r0 = *(u64 *)(r6 + 0); \ + exit; \ +" : + : + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_unpriv.c b/tools/testing/selftests/bpf/progs/verifier_unpriv.c index 49f7bd05edad..42de5cff7e52 100644 --- a/tools/testing/selftests/bpf/progs/verifier_unpriv.c +++ b/tools/testing/selftests/bpf/progs/verifier_unpriv.c @@ -6,6 +6,8 @@ #include "../../../include/linux/filter.h" #include "bpf_misc.h" +extern const int bpf_prog_active __ksym; + #define BPF_SK_LOOKUP(func) \ /* struct bpf_sock_tuple tuple = {} */ \ "r2 = 0;" \ @@ -78,6 +80,23 @@ __naked void dummy_prog_loop1_socket(void) } SEC("socket") +__description("unpriv: pseudo btf id log masks address") +__success_unpriv +__msg_unpriv("0: (18) r1 = 0x0") +__not_msg_unpriv("0: (18) r1 = 0x{{[1-9a-f][0-9a-f]*}}") +__retval_unpriv(0) +__log_level(2) +__naked void pseudo_btf_id_log_masks_address(void) +{ + asm volatile ("r1 = %[bpf_prog_active] ll;" + "r0 = 0;" + "exit;" + : + : __imm_addr(bpf_prog_active) + : __clobber_all); +} + +SEC("socket") __description("unpriv: return pointer") __success __failure_unpriv __msg_unpriv("R0 leaks addr") __retval(POINTER_VALUE) diff --git a/tools/testing/selftests/bpf/test_maps.c b/tools/testing/selftests/bpf/test_maps.c index c32da7bd8be2..6a2641ee7897 100644 --- a/tools/testing/selftests/bpf/test_maps.c +++ b/tools/testing/selftests/bpf/test_maps.c @@ -759,16 +759,15 @@ static void test_sockmap(unsigned int tasks, void *data) goto out_sockmap; } - /* Test update with unsupported UDP socket */ + /* Test update with unsupported unbound UDP socket */ udp = socket(AF_INET, SOCK_DGRAM, 0); - i = 0; - err = bpf_map_update_elem(fd, &i, &udp, BPF_ANY); - if (err) { - printf("Failed socket update SOCK_DGRAM '%i:%i'\n", - i, udp); + CHECK(udp < 0, "socket(AF_INET, SOCK_DGRAM)", "errno:%d\n", errno); + err = bpf_map_update_elem(fd, &(int){0}, &udp, BPF_ANY); + close(udp); + if (!err) { + printf("Unexpectedly succeeded unbound UDP update '0:%i'\n", udp); goto out_sockmap; } - close(udp); /* Test update without programs */ for (i = 0; i < 6; i++) { diff --git a/tools/testing/selftests/bpf/usdt.h b/tools/testing/selftests/bpf/usdt.h index c71e21df38b3..75687f50f4e2 100644 --- a/tools/testing/selftests/bpf/usdt.h +++ b/tools/testing/selftests/bpf/usdt.h @@ -313,7 +313,7 @@ struct usdt_sema { volatile unsigned short active; }; #if defined(__ia64__) || defined(__s390__) || defined(__s390x__) #define USDT_NOP nop 0 #elif defined(__x86_64__) -#define USDT_NOP .byte 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x0 /* nop, nop5 */ +#define USDT_NOP .byte 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 /* nop, nop10 */ #else #define USDT_NOP nop #endif diff --git a/tools/testing/selftests/bpf/usdt_2.c b/tools/testing/selftests/bpf/usdt_2.c index 789883aaca4c..5e38f8605b02 100644 --- a/tools/testing/selftests/bpf/usdt_2.c +++ b/tools/testing/selftests/bpf/usdt_2.c @@ -3,7 +3,7 @@ #if defined(__x86_64__) /* - * Include usdt.h with default nop,nop5 instructions combo. + * Include usdt.h with default nop,nop10 instructions combo. */ #include "usdt.h" @@ -13,4 +13,17 @@ void usdt_2(void) USDT(optimized_attach, usdt_2); } +static volatile unsigned long usdt_red_zone_arg1 = 0xDEADBEEF; +static volatile unsigned long usdt_red_zone_arg2 = 0xCAFEBABE; +static volatile unsigned long usdt_red_zone_arg3 = 0xFEEDFACE; + +void __attribute__((noinline)) usdt_red_zone_trigger(void) +{ + unsigned long a1 = usdt_red_zone_arg1; + unsigned long a2 = usdt_red_zone_arg2; + unsigned long a3 = usdt_red_zone_arg3; + + USDT(optimized_attach, usdt_red_zone, a1, a2, a3); +} + #endif diff --git a/tools/testing/selftests/clone3/clone3_set_tid.c b/tools/testing/selftests/clone3/clone3_set_tid.c index 5c944aee6b41..485efa7c9eed 100644 --- a/tools/testing/selftests/clone3/clone3_set_tid.c +++ b/tools/testing/selftests/clone3/clone3_set_tid.c @@ -141,7 +141,7 @@ int main(int argc, char *argv[]) { FILE *f; char buf; - char *line; + char *line = NULL; int status; int ret = -1; size_t len = 0; diff --git a/tools/testing/selftests/cpufreq/governor.sh b/tools/testing/selftests/cpufreq/governor.sh index fe37df79c087..cf59e63f8e14 100755 --- a/tools/testing/selftests/cpufreq/governor.sh +++ b/tools/testing/selftests/cpufreq/governor.sh @@ -16,6 +16,12 @@ source cpufreq.sh CUR_GOV= CUR_FREQ= +# Per-policy backup, keyed by policy so multiple policies can be saved at once +# (backup_governor/restore_governor also keep CUR_GOV/CUR_FREQ for callers that +# read them directly). +declare -A SAVED_GOVERNORS +declare -A SAVED_FREQS + # Find governor's directory path # $1: policy, $2: governor find_gov_directory() @@ -39,11 +45,13 @@ find_current_governor() backup_governor() { CUR_GOV=$(find_current_governor $1) + SAVED_GOVERNORS[$1]=$CUR_GOV printf "Governor backup done for $1: $CUR_GOV\n" if [ $CUR_GOV == "userspace" ]; then CUR_FREQ=$(find_current_freq $1) + SAVED_FREQS[$1]=$CUR_FREQ printf "Governor frequency backup done for $1: $CUR_FREQ\n" fi @@ -53,11 +61,13 @@ backup_governor() # $1: policy restore_governor() { + CUR_GOV=${SAVED_GOVERNORS[$1]} __switch_governor $1 $CUR_GOV printf "Governor restored for $1 to $CUR_GOV\n" if [ $CUR_GOV == "userspace" ]; then + CUR_FREQ=${SAVED_FREQS[$1]} set_cpu_frequency $1 $CUR_FREQ printf "Governor frequency restored for $1: $CUR_FREQ\n" fi @@ -65,6 +75,17 @@ restore_governor() printf "\n" } +# Save/restore governors for every policy at once +save_all_governors() +{ + for_each_policy backup_governor +} + +restore_all_governors() +{ + for_each_policy restore_governor +} + # param: # $1: policy, $2: governor __switch_governor() @@ -100,11 +121,6 @@ switch_governor() # $1: policy, $2: governor switch_show_governor() { - cur_gov=find_current_governor - if [ $cur_gov == "userspace" ]; then - cur_freq=find_current_freq - fi - # switch governor __switch_governor $1 $2 diff --git a/tools/testing/selftests/cpufreq/special-tests.sh b/tools/testing/selftests/cpufreq/special-tests.sh index 8d40505dc468..e87ed7c8e5e5 100755 --- a/tools/testing/selftests/cpufreq/special-tests.sh +++ b/tools/testing/selftests/cpufreq/special-tests.sh @@ -40,7 +40,9 @@ simple_lockdep() { printf "** Test: Running ${FUNCNAME[0]} **\n" + save_all_governors for_each_policy __simple_lockdep + restore_all_governors } # Test 2 @@ -56,7 +58,10 @@ concurrent_lockdep() { printf "** Test: Running ${FUNCNAME[0]} **\n" + save_all_governors for_each_policy_concurrent __concurrent_lockdep + wait + restore_all_governors } # Test 3 @@ -65,20 +70,26 @@ quick_shuffle() # this is called concurrently from governor_race for I in `seq 1000` do - echo ondemand | sudo tee $CPUFREQROOT/policy*/scaling_governor & - echo userspace | sudo tee $CPUFREQROOT/policy*/scaling_governor & + echo ondemand | tee $CPUFREQROOT/policy*/scaling_governor & + echo userspace | tee $CPUFREQROOT/policy*/scaling_governor & done + wait } governor_race() { printf "** Test: Running ${FUNCNAME[0]} **\n" + save_all_governors + # run 8 concurrent instances for I in `seq 8` do quick_shuffle & done + wait + + restore_all_governors } # Test 4 @@ -112,5 +123,8 @@ hotplug_with_updates_cpu() hotplug_with_updates() { + save_all_governors for_each_non_boot_cpu hotplug_with_updates_cpu + wait + restore_all_governors } diff --git a/tools/testing/selftests/drivers/net/config b/tools/testing/selftests/drivers/net/config index 91d4fd410914..2070e890e064 100644 --- a/tools/testing/selftests/drivers/net/config +++ b/tools/testing/selftests/drivers/net/config @@ -4,6 +4,8 @@ CONFIG_DEBUG_INFO_BTF_MODULES=n CONFIG_INET_PSP=y CONFIG_IPV6=y CONFIG_MACSEC=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_CLS_BPF=y CONFIG_NETCONSOLE=m CONFIG_NETCONSOLE_DYNAMIC=y CONFIG_NETCONSOLE_EXTENDED_LOG=y @@ -11,6 +13,7 @@ CONFIG_NETDEVSIM=m CONFIG_NETKIT=y CONFIG_NET_SCH_ETF=m CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_INGRESS=y CONFIG_PPP=y CONFIG_PPPOE=y CONFIG_VLAN_8021Q=m diff --git a/tools/testing/selftests/drivers/net/hw/tso.py b/tools/testing/selftests/drivers/net/hw/tso.py index 1b789fea8929..802bb4868046 100755 --- a/tools/testing/selftests/drivers/net/hw/tso.py +++ b/tools/testing/selftests/drivers/net/hw/tso.py @@ -187,28 +187,24 @@ def query_nic_features(cfg) -> None: cfg.wanted_features.add(f["name"]) cfg.hw_features = set() - hw_all_features_cmd = "" for f in features["hw"]["bits"]["bit"]: if f.get("value", False): - feature = f["name"] - cfg.hw_features.add(feature) - hw_all_features_cmd += f" {feature} on" - try: - ethtool(f"-K {cfg.ifname} {hw_all_features_cmd}") - except Exception as e: - ksft_pr(f"WARNING: failure enabling all hw features: {e}") - ksft_pr("partial gso feature detection may be impacted") + cfg.hw_features.add(f["name"]) # Check which features are supported via GSO partial cfg.partial_features = set() if 'tx-gso-partial' in cfg.hw_features: + seg_features = {f for f in cfg.hw_features if "segmentation" in f} + ethtool(f"-K {cfg.ifname} " + + " ".join(f"{f} on" for f in seg_features)) + ethtool(f"-K {cfg.ifname} tx-gso-partial off") no_partial = set() features = cfg.ethnl.features_get({"header": {"dev-index": cfg.ifindex}}) for f in features["active"]["bits"]["bit"]: no_partial.add(f["name"]) - cfg.partial_features = cfg.hw_features - no_partial + cfg.partial_features = seg_features - no_partial ethtool(f"-K {cfg.ifname} tx-gso-partial on") restore_wanted_features(cfg) diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh index 96d704b8d9d9..4436567abc94 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_cmdline.sh @@ -50,7 +50,7 @@ do # Send the message echo "${MSG}: ${TARGET}" > /dev/kmsg # Wait until socat saves the file to disk - busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true # Make sure the message was received in the dst part # and exit validate_msg "${OUTPUT_FILE}" diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh index 0dc7280c3080..fc3db40c1df5 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_fragmented_msg.sh @@ -104,7 +104,7 @@ wait_local_port_listen "${NAMESPACE}" "${PORT}" udp # Send the message echo "${MSG}: ${TARGET}" > /dev/kmsg # Wait until socat saves the file to disk -busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" +busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true # Check if the message was not corrupted validate_fragmented_result "${OUTPUT_FILE}" @@ -117,6 +117,6 @@ disable_release_append listen_port_and_save_to "${OUTPUT_FILE}" & wait_local_port_listen "${NAMESPACE}" "${PORT}" udp echo "${MSG}: ${TARGET}" > /dev/kmsg -busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" +busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true validate_fragmented_result "${OUTPUT_FILE}" exit "${ksft_pass}" diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh index cb59cf436dd0..b379dff9087e 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_resume.sh @@ -44,7 +44,8 @@ function trigger_reactivation() { # Restore MACs ip netns exec "${NAMESPACE}" ip link set "${DSTIF}" \ address "${SAVED_DSTMAC}" - if [ "${BINDMODE}" == "mac" ]; then + if [ "${BINDMODE}" == "mac" ] && + [ "$(mac_get "${SRCIF}")" != "${SAVED_SRCMAC}" ]; then ip link set dev "${SRCIF}" down ip link set dev "${SRCIF}" address "${SAVED_SRCMAC}" # Rename device in order to trigger target resume, as initial @@ -107,7 +108,7 @@ do # Send the message echo "${MSG}: ${TARGET}" > /dev/kmsg # Wait until socat saves the file to disk - busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true # Make sure the message was received in the dst part # and exit validate_msg "${OUTPUT_FILE}" diff --git a/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh b/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh index 3fb8c4afe3d2..7089f7bd1e34 100755 --- a/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh +++ b/tools/testing/selftests/drivers/net/netconsole/netcons_sysdata.sh @@ -197,7 +197,7 @@ function runtest { # Send the message taskset -c "${CPU}" echo "${MSG}: ${TARGET}" > /dev/kmsg # Wait until socat saves the file to disk - busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" || true } # ========== # diff --git a/tools/testing/selftests/drivers/net/settings b/tools/testing/selftests/drivers/net/settings new file mode 100644 index 000000000000..eef533824a3c --- /dev/null +++ b/tools/testing/selftests/drivers/net/settings @@ -0,0 +1 @@ +timeout=360 diff --git a/tools/testing/selftests/drivers/ntsync/config b/tools/testing/selftests/drivers/ntsync/config index 60539c826d06..0aa68de147af 100644 --- a/tools/testing/selftests/drivers/ntsync/config +++ b/tools/testing/selftests/drivers/ntsync/config @@ -1 +1 @@ -CONFIG_WINESYNC=y +CONFIG_NTSYNC=y diff --git a/tools/testing/selftests/exec/.gitignore b/tools/testing/selftests/exec/.gitignore index 7f3d1ae762ec..e42ecd4c908d 100644 --- a/tools/testing/selftests/exec/.gitignore +++ b/tools/testing/selftests/exec/.gitignore @@ -19,3 +19,14 @@ null-argv xxxxxxxx* pipe S_I*.test +binfmt_misc_bpf +binfmt_misc_interplimit +binfmt_bpf_interp +binfmt_bpf_app +binfmt_misc_transparent +binfmt_transparent_interp +binfmt_misc_loader +binfmt_loader_payload +binfmt_loader_payload_static +*.bpf.o +vmlinux.h diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile index 45a3cfc435cf..b640af8f02b5 100644 --- a/tools/testing/selftests/exec/Makefile +++ b/tools/testing/selftests/exec/Makefile @@ -21,9 +21,56 @@ TEST_GEN_PROGS += recursion-depth TEST_GEN_PROGS += null-argv TEST_GEN_PROGS += check-exec +# binfmt_misc must not be reachable as an exec source or as a stacking layer, +# or an 'F' entry can pin the instance that owns it. Unprivileged, no bpf. +TEST_GEN_PROGS += binfmt_misc_selfpin + +# The interpreters an 'F' or 'B' entry pre-opens are charged against +# UCOUNT_BINFMT_MISC_INTERPRETERS. Unprivileged, no bpf. +TEST_GEN_PROGS += binfmt_misc_interplimit + +# 'D' (register disabled) binfmt_misc test: an entry that exists but does +# not dispatch until it is enabled. Static magic entry, no bpf toolchain. +TEST_GEN_PROGS += binfmt_misc_disabled + +# Static ('T' flag) transparent binfmt_misc test; the asserting interpreter +# is shared with the bpf harness's transparent case. No bpf toolchain needed. +TEST_GEN_PROGS += binfmt_misc_transparent +TEST_GEN_FILES += binfmt_transparent_interp + +# 'L' (loader substitution) binfmt_misc test: the payload runs as the main +# image with a copy of the system loader substituted for its PT_INTERP and +# asserts the native identity from inside; the static build proves the +# override is dropped for a binary without PT_INTERP. +TEST_GEN_PROGS += binfmt_misc_loader +TEST_GEN_FILES += binfmt_loader_payload binfmt_loader_payload_static + +# binfmt_misc bpf-backed ('B') handler test: a libbpf harness plus its +# struct_ops objects and the test interpreter/app it routes between. Only +# built when clang, bpftool, the vmlinux BTF and libbpf are all present +# (HAVE_BPF_TOOLCHAIN=y forces it) so the other exec selftests don't grow +# a bpf toolchain dependency. +CLANG ?= clang +BPFTOOL ?= bpftool +VMLINUX_BTF ?= /sys/kernel/btf/vmlinux +HAVE_BPF_TOOLCHAIN ?= $(shell command -v $(CLANG) >/dev/null 2>&1 && \ + command -v $(BPFTOOL) >/dev/null 2>&1 && \ + test -r $(VMLINUX_BTF) && \ + pkg-config --exists libbpf 2>/dev/null && echo y) +ifeq ($(HAVE_BPF_TOOLCHAIN),y) +TEST_GEN_PROGS += binfmt_misc_bpf +TEST_GEN_FILES += bpf_interp.bpf.o nix_origin.bpf.o transparent.bpf.o +TEST_GEN_FILES += loader.bpf.o interp_bind.bpf.o +TEST_GEN_FILES += binfmt_bpf_interp binfmt_bpf_app binfmt_bind_interp +else +$(info exec selftests: skipping binfmt_misc_bpf, needs clang, bpftool, vmlinux BTF and libbpf) +endif + EXTRA_CLEAN := $(OUTPUT)/subdir.moved $(OUTPUT)/execveat.moved $(OUTPUT)/xxxxx* \ $(OUTPUT)/S_I*.test +LOCAL_HDRS += binfmt_misc_common.h + include ../lib.mk CHECK_EXEC_SAMPLES := $(top_srcdir)/samples/check-exec @@ -55,3 +102,49 @@ $(OUTPUT)/script-exec.inc: $(CHECK_EXEC_SAMPLES)/script-exec.inc cp $< $@ $(OUTPUT)/script-noexec.inc: $(CHECK_EXEC_SAMPLES)/script-noexec.inc cp $< $@ + +# Reuses setup_userns()/write_file() from the filesystems selftests. Their +# wrappers.h wants the uapi headers, so ask for them here rather than widening +# CFLAGS for every program in this directory. +$(OUTPUT)/binfmt_misc_selfpin: CFLAGS += $(TOOLS_INCLUDES) +$(OUTPUT)/binfmt_misc_selfpin: ../filesystems/utils.c +$(OUTPUT)/binfmt_misc_interplimit: CFLAGS += $(TOOLS_INCLUDES) +$(OUTPUT)/binfmt_misc_interplimit: ../filesystems/utils.c + +# --- binfmt_misc bpf ('B') handler test --------------------------------- +# The struct_ops bpf objects are compiled against the running kernel's BTF. +# CLANG/BPFTOOL/VMLINUX_BTF are set above next to the toolchain check; +# override LIBBPF_CFLAGS/LDLIBS to point at a libbpf install. +BPF_CFLAGS ?= -I$(OUTPUT) +LIBBPF_CFLAGS ?= +LIBBPF_LDLIBS ?= -lbpf -lelf -lz + +$(OUTPUT)/vmlinux.h: + $(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@ + +# BPF_NO_KFUNC_PROTOTYPES: the programs declare the kfuncs they use themselves. +$(OUTPUT)/%.bpf.o: %.bpf.c $(OUTPUT)/vmlinux.h + $(CLANG) -g -O2 -target bpf -mcpu=v3 -DBPF_NO_KFUNC_PROTOTYPES \ + $(BPF_CFLAGS) $(LIBBPF_CFLAGS) -c $< -o $@ + +$(OUTPUT)/binfmt_misc_bpf: binfmt_misc_bpf.c binfmt_misc_common.h + $(CC) $(CFLAGS) $(LIBBPF_CFLAGS) $(LDFLAGS) $< $(LIBBPF_LDLIBS) -o $@ + +$(OUTPUT)/binfmt_bpf_interp: binfmt_bpf_interp.c + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +$(OUTPUT)/binfmt_bind_interp: binfmt_bind_interp.c + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +$(OUTPUT)/binfmt_loader_payload: binfmt_loader_payload.c binfmt_misc_common.h + $(CC) $(CFLAGS) $(LDFLAGS) -fPIE -pie $< -o $@ + +$(OUTPUT)/binfmt_loader_payload_static: binfmt_loader_payload.c binfmt_misc_common.h + $(CC) $(CFLAGS) $(LDFLAGS) -static $< -o $@ + +# PT_INTERP is set to the literal "$ORIGIN/binfmt_bpf_interp"; the nix_origin +# handler resolves it relative to the binary at run time. +$(OUTPUT)/binfmt_bpf_app: binfmt_bpf_app.c + $(CC) $(CFLAGS) $(LDFLAGS) -Wl,--dynamic-linker,'$$ORIGIN/binfmt_bpf_interp' $< -o $@ + +EXTRA_CLEAN += $(OUTPUT)/vmlinux.h $(OUTPUT)/*.bpf.o diff --git a/tools/testing/selftests/exec/binfmt_bind_interp.c b/tools/testing/selftests/exec/binfmt_bind_interp.c new file mode 100644 index 000000000000..06d65062856b --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_bind_interp.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test interpreter for the bound-interpreter case of the binfmt_misc_bpf + * selftest. Two copies are installed at different paths and bound to one + * entry under different names; printing argv[0] - the path the kernel ran + * this copy under - tells the harness which of them the load program picked. + */ +#include <stdio.h> + +int main(int argc, char **argv) +{ + printf("BIND_RAN %s\n", argc > 0 ? argv[0] : ""); + return 0; +} diff --git a/tools/testing/selftests/exec/binfmt_bpf_app.c b/tools/testing/selftests/exec/binfmt_bpf_app.c new file mode 100644 index 000000000000..472270f148bc --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_bpf_app.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A relocatable binary for the binfmt_misc_bpf $ORIGIN case. The Makefile + * links it with PT_INTERP set to the literal "$ORIGIN/binfmt_bpf_interp" + * (-Wl,--dynamic-linker), which the kernel ELF loader cannot resolve. The + * nix_origin bpf handler resolves it relative to this binary's directory and + * routes execution to the co-located interpreter. + */ +int main(void) +{ + return 0; +} diff --git a/tools/testing/selftests/exec/binfmt_bpf_interp.c b/tools/testing/selftests/exec/binfmt_bpf_interp.c new file mode 100644 index 000000000000..2db205f095b2 --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_bpf_interp.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test interpreter for the binfmt_misc_bpf selftest. A bpf-backed 'B' handler + * routes a matched binary here; printing this marker proves the program's + * chosen interpreter actually ran. + */ +#include <unistd.h> + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + write(1, "BPF_INTERP_RAN\n", 15); + return 0; +} diff --git a/tools/testing/selftests/exec/binfmt_loader_payload.c b/tools/testing/selftests/exec/binfmt_loader_payload.c new file mode 100644 index 000000000000..272db8efb4b5 --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_loader_payload.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Payload for the binfmt_misc 'L' (loader substitution) selftest. It is + * executed as the MAIN image - a fully native exec - with the registered + * interpreter substituted for its PT_INTERP, and asserts the native + * identity from the inside. Exits 0 when every surface checks out. + * + * Modes, selected by the orchestrator via the environment: + * - default: full assertions, path-based ones included + * - BINFMT_TEST_MEMFD=1: executed from an inaccessible memfd, skip + * the path-based assertions + * - BINFMT_TEST_STATIC=1: static build; the override was dropped, so + * expect no interpreter at all + */ +#define _GNU_SOURCE +#include <elf.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/auxv.h> +#include <unistd.h> + +#include "binfmt_misc_common.h" + +/* Start of our own mapped image, courtesy of the linker. */ +extern const char __ehdr_start[]; + +/* An image is never this large; used to bracket "within our image". */ +#define IMAGE_SPAN (16UL << 20) + +static int failed; + +static void check(int cond, const char *what) +{ + if (cond) + return; + fprintf(stderr, "[payload] FAILED: %s (errno %d)\n", what, errno); + failed = 1; +} + +/* Return whether /proc/self/maps names a path starting with @prefix. */ +static int maps_has_prefix(const char *prefix) +{ + char *line = NULL; + size_t len = 0; + int found = 0; + FILE *f; + + f = fopen("/proc/self/maps", "r"); + if (!f) + return -1; + while (getline(&line, &len, f) > 0) { + char *path = strchr(line, '/'); + + if (path && !strncmp(path, prefix, strlen(prefix))) { + found = 1; + break; + } + } + free(line); + fclose(f); + return found; +} + +int main(int argc, char *argv[]) +{ + const char *binary = getenv("BINFMT_TEST_BINARY"); + const char *interp = getenv("BINFMT_TEST_INTERP"); + int memfd_mode = getenv("BINFMT_TEST_MEMFD") != NULL; + int static_mode = getenv("BINFMT_TEST_STATIC") != NULL; + unsigned long self = (unsigned long)__ehdr_start; + unsigned long base = getauxval(AT_BASE); + unsigned long phdr = getauxval(AT_PHDR); + unsigned long entry = getauxval(AT_ENTRY); + unsigned long start_code, end_code; + + /* The argument vector is exactly what the caller built. */ + check(argc == 3 && !strcmp(argv[0], PAYLOAD_ARGV0) && + !strcmp(argv[1], PAYLOAD_ARG1) && !strcmp(argv[2], PAYLOAD_ARG2), + "argv was rewritten"); + + /* Native from birth: no execfd, no dispatch marker. */ + check(getauxval(AT_EXECFD) == 0, "AT_EXECFD present"); + check(getauxval(AT_FLAGS) == 0, "AT_FLAGS not native"); + + if (static_mode) { + /* The override was dropped: no interpreter was loaded. */ + check(base == 0, "AT_BASE set for a static payload"); + } else { + /* A loader is mapped in the interpreter slot, not our image. */ + check(base != 0, "AT_BASE missing"); + check(base < self || base >= self + IMAGE_SPAN, + "AT_BASE inside our own image"); + } + + /* We occupy the main-image slot. */ + check(phdr >= self && phdr < self + IMAGE_SPAN, + "AT_PHDR outside our image"); + check(entry >= self && entry < self + IMAGE_SPAN, + "AT_ENTRY outside our image"); + + /* The code statistics markers describe our image, natively placed. */ + if (stat_codes(getpid(), &start_code, &end_code) == 0) { + check(start_code >= self && start_code < end_code && + end_code < self + IMAGE_SPAN, + "stat start_code/end_code not our image"); + check(entry >= start_code && entry < end_code, + "AT_ENTRY outside [start_code, end_code)"); + } else { + check(0, "cannot parse /proc/self/stat"); + } + + if (!memfd_mode && binary) { + const char *execfn = (const char *)getauxval(AT_EXECFN); + const char *base_name = strrchr(binary, '/'); + + base_name = base_name ? base_name + 1 : binary; + + /* exe link, AT_EXECFN and comm all follow the binary. */ + check(exe_is(binary), "/proc/self/exe"); + check(execfn && !strcmp(execfn, binary), "AT_EXECFN"); + check(comm_is(base_name), "comm"); + + /* The running binary is write-denied, natively. */ + check(write_denied(binary), "no ETXTBSY on the binary"); + } + + if (interp) { + int found = maps_has_prefix(interp); + + if (static_mode) + /* Nothing was substituted, nothing may be mapped. */ + check(found == 0, "loader mapped for a static payload"); + else + /* The substituted loader shows under its real path. */ + check(found == 1, "loader path not in /proc/self/maps"); + } + + if (failed) + return 1; + printf("[payload] native identity checks out\n"); + return 0; +} diff --git a/tools/testing/selftests/exec/binfmt_misc_bpf.c b/tools/testing/selftests/exec/binfmt_misc_bpf.c new file mode 100644 index 000000000000..b2a4518901b0 --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_misc_bpf.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Selftest for binfmt_misc bpf-backed ('B') handlers. + * + * A handler is a struct binfmt_misc_ops struct_ops map with a sleepable match + * and a sleepable load program. Attaching it publishes it by name in the + * caller's user namespace; a 'B' entry referencing it by name in the + * interpreter field activates it: + * + * echo ':name:B::::<handler>:' > /proc/sys/fs/binfmt_misc/register + * + * Five self-contained cases are exercised: + * + * 1. bpf_interp: the match program matches a synthetic aarch64 ELF header + * from the prefetched bprm->buf and the load program routes it to a + * fixed interpreter of its choosing. + * 2. nix_origin: the match program reads the binary's program headers to + * commit only to a "$ORIGIN/..."-relative PT_INTERP and the load program + * resolves it to an interpreter co-located with the binary (the + * relocatable-loader case the kernel ELF loader cannot express). + * 3. transparent: the load program sets BPF_BINPRM_TRANSPARENT; the + * asserting interpreter (binfmt_transparent_interp) verifies the + * identity the kernel constructed (exe link, argv, cmdline, comm, + * AT_EXECFD, write denial) from inside the process. + * 4. loader: the load program sets BPF_BINPRM_LOADER; the payload + * (binfmt_loader_payload) runs as the main image with the selected + * interpreter substituted for its PT_INTERP and asserts the native + * identity from inside. + * 5. interp_bind: an entry registered disabled with 'D' is given its + * interpreters one write at a time, and the load program picks one by + * name per exec. Replacing what the path holds afterwards changes + * nothing, which is the point of binding a file rather than resolving + * a name at exec time. Enabling the entry seals it. + * + * The first two route to a test interpreter that prints BPF_INTERP_RAN, + * proving the program's chosen interpreter actually ran. + */ +#define _GNU_SOURCE +#include <elf.h> +#include <limits.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#include <bpf/btf.h> +#include <bpf/libbpf.h> + +#include "binfmt_misc_common.h" +#include "kselftest_harness.h" + +#define INTERP_PATH "/tmp/binfmt_bpf_interp" +#define AARCH64_PATH "/tmp/binfmt_bpf_aarch64" +#define RELOC_TEMPLATE "/tmp/binfmt_relocXXXXXX" +#define TRANS_INTERP "/tmp/binfmt_transparent_interp" +#define TRANS_PATH "/tmp/binfmt_bpf_riscv" +#define EXPECT "BPF_INTERP_RAN" +#define TRANS_EXPECT "TRANSPARENT_OK" +#define LOADER_INTERP "/tmp/binfmt_loader_interp" +#define LOADER_PATH "/tmp/binfmt_bpf_loader.ldrtest" +#define BIND_FIRST "/tmp/binfmt_bind_first" +#define BIND_SECOND "/tmp/binfmt_bind_second" +#define BIND_ARM_PATH "/tmp/binfmt_bind_arm" +#define BIND_RISCV_PATH "/tmp/binfmt_bind_riscv" +#define BIND_EXPECT "BIND_RAN " +#define BIND_MAX 100 +#define INTERP_LIMIT "/proc/sys/user/max_binfmt_misc_interpreters" +/* Exit status of the binding child when it cannot set up a budget of its own. */ +#define BIND_NO_BUDGET 200 + +/* A minimal 64-bit little-endian ELF header, padded to the read size. */ +static int create_fake_elf(const char *path, unsigned short machine) +{ + unsigned char hdr[256] = {0}; + int fd; + + hdr[0] = 0x7f; hdr[1] = 'E'; hdr[2] = 'L'; hdr[3] = 'F'; + hdr[4] = ELFCLASS64; + hdr[5] = ELFDATA2LSB; + hdr[6] = EV_CURRENT; + hdr[16] = ET_EXEC; + hdr[18] = machine & 0xff; /* e_machine, little-endian */ + hdr[19] = machine >> 8; + hdr[20] = EV_CURRENT; + + unlink(path); + fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0755); + if (fd < 0) + return -1; + if (write(fd, hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) { + close(fd); + return -1; + } + close(fd); + return 0; +} + +/* + * Register a 'B' entry for @handler. With @flags "D" the entry is created + * disabled, which is what leaves it open to being given interpreters. + */ +static int register_entry(const char *name, const char *handler, + const char *flags) +{ + char rule[PATH_MAX]; + + snprintf(rule, sizeof(rule), ":%s:B::::%s:%s", name, handler, + flags ? flags : ""); + return write_reg(rule); +} + +static int check_output(const char *cmd, const char *expected) +{ + char buf[128]; + FILE *fp; + + fp = popen(cmd, "r"); + if (!fp) + return -1; + if (!fgets(buf, sizeof(buf), fp)) { + pclose(fp); + return -1; + } + pclose(fp); + return strncmp(buf, expected, strlen(expected)) ? -1 : 0; +} + +/* Does the kernel BTF know struct binfmt_misc_ops (CONFIG_BINFMT_MISC_BPF)? */ +static bool have_binfmt_misc_ops(void) +{ + struct btf *btf = btf__load_vmlinux_btf(); + bool have; + + have = btf && btf__find_by_name_kind(btf, "binfmt_misc_ops", + BTF_KIND_STRUCT) >= 0; + btf__free(btf); + return have; +} + +/* The reason bpf handler cases cannot run here, NULL if they can. */ +static const char *bpf_handler_unsupported(void) +{ + if (getuid() != 0) + return "test must be run as root"; + if (!have_binfmt_misc_ops()) + return "no struct binfmt_misc_ops in the kernel BTF (CONFIG_BINFMT_MISC_BPF)"; + if (!binfmt_misc_available()) + return "no binfmt_misc"; + return NULL; +} + +/* An attached handler with its 'B' entry activated. */ +struct bpf_case { + struct bpf_object *obj; + struct bpf_link *link; + const char *entry; +}; + +/* + * Load @objfile, attach its struct_ops map @handler (which publishes the + * handler) and register a 'B' entry named @entry that references it, with + * @flags as the entry's register-string flags. + */ +static int bpf_case_start_flags(struct bpf_case *c, const char *objfile, + const char *handler, const char *entry, + const char *flags) +{ + struct bpf_map *map; + + c->obj = NULL; + c->link = NULL; + c->entry = entry; + + c->obj = bpf_object__open_file(objfile, NULL); + if (!c->obj || libbpf_get_error(c->obj)) { + fprintf(stderr, "open %s failed\n", objfile); + c->obj = NULL; + return -1; + } + if (bpf_object__load(c->obj)) { + fprintf(stderr, "load %s failed (check dmesg for the verifier log)\n", + objfile); + goto fail; + } + map = bpf_object__find_map_by_name(c->obj, handler); + if (!map) { + fprintf(stderr, "no struct_ops map '%s' in %s\n", handler, objfile); + goto fail; + } + c->link = bpf_map__attach_struct_ops(map); + if (!c->link || libbpf_get_error(c->link)) { + fprintf(stderr, "attach struct_ops '%s' failed\n", handler); + c->link = NULL; + goto fail; + } + if (register_entry(entry, handler, flags)) { + fprintf(stderr, "register 'B' entry '%s' failed\n", entry); + goto fail; + } + return 0; + +fail: + bpf_link__destroy(c->link); + bpf_object__close(c->obj); + c->obj = NULL; + c->link = NULL; + return -1; +} + +static int bpf_case_start(struct bpf_case *c, const char *objfile, + const char *handler, const char *entry) +{ + return bpf_case_start_flags(c, objfile, handler, entry, NULL); +} + +static void bpf_case_stop(struct bpf_case *c) +{ + unregister(c->entry); + bpf_link__destroy(c->link); + bpf_object__close(c->obj); +} + +/* Activate @handler, run @target and check it produced @expect. */ +static int run_case(const char *objfile, const char *handler, + const char *entry, const char *target, const char *expect) +{ + struct bpf_case c; + int ret; + + if (bpf_case_start(&c, objfile, handler, entry)) + return -1; + ret = check_output(target, expect); + bpf_case_stop(&c); + return ret; +} + +FIXTURE(bpf_handler) { + char obj[PATH_MAX]; /* struct_ops object of the case under test */ +}; + +FIXTURE_SETUP(bpf_handler) +{ + char src[PATH_MAX]; + const char *why = bpf_handler_unsupported(); + + if (why) + SKIP(return, "%s", why); + + /* Shared test interpreter. */ + ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_interp"), 0); + ASSERT_EQ(copy_file(src, INTERP_PATH), 0); +} + +FIXTURE_TEARDOWN(bpf_handler) +{ + unlink(INTERP_PATH); +} + +/* The match program matches a synthetic header, the load program routes it. */ +TEST_F(bpf_handler, fixed_interpreter) +{ + ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0); + ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj), + "bpf_interp.bpf.o"), 0); + EXPECT_EQ(run_case(self->obj, "bpf_interp", "test_bpf_interp", + AARCH64_PATH, EXPECT), 0); + unlink(AARCH64_PATH); +} + +/* A "$ORIGIN/..." PT_INTERP resolved to an interpreter next to the binary. */ +TEST_F(bpf_handler, origin_relative_interpreter) +{ + char src[PATH_MAX], app[PATH_MAX], interp[PATH_MAX]; + char dir[] = RELOC_TEMPLATE; + + ASSERT_NE(mkdtemp(dir), NULL); + snprintf(app, sizeof(app), "%s/app", dir); + snprintf(interp, sizeof(interp), "%s/binfmt_bpf_interp", dir); + ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_app"), 0); + ASSERT_EQ(copy_file(src, app), 0); + ASSERT_EQ(copy_file(INTERP_PATH, interp), 0); + + ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj), + "nix_origin.bpf.o"), 0); + EXPECT_EQ(run_case(self->obj, "nix_origin", "test_bpf_origin", + app, EXPECT), 0); + + unlink(app); + unlink(interp); + rmdir(dir); +} + +/* A transparent dispatch: the process presents as the binary, not the interp. */ +TEST_F(bpf_handler, transparent_dispatch) +{ + char src[PATH_MAX], cmd[PATH_MAX + 16]; + + /* Probe for transparent-mode support via its static counterpart. */ + if (!binfmt_flag_supported('T')) + SKIP(return, "kernel without transparent mode"); + + ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0); + ASSERT_EQ(copy_file(src, TRANS_INTERP), 0); + ASSERT_EQ(create_fake_elf(TRANS_PATH, EM_RISCV), 0); + + setenv("BINFMT_TEST_BINARY", TRANS_PATH, 1); + snprintf(cmd, sizeof(cmd), "%s argone argtwo", TRANS_PATH); + ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj), + "transparent.bpf.o"), 0); + EXPECT_EQ(run_case(self->obj, "transparent", "test_bpf_transparent", + cmd, TRANS_EXPECT), 0); + + unlink(TRANS_PATH); + unlink(TRANS_INTERP); +} + +/* A per-exec loader substitution: the payload runs as a native exec. */ +TEST_F(bpf_handler, loader_substitution) +{ + char src[PATH_MAX], loader[PATH_MAX]; + struct bpf_case c; + int status; + + if (find_loader(loader, sizeof(loader))) + SKIP(return, "cannot determine own PT_INTERP"); + + ASSERT_EQ(copy_file(loader, LOADER_INTERP), 0); + ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0); + ASSERT_EQ(copy_file(src, LOADER_PATH), 0); + ASSERT_EQ(patch_file(LOADER_PATH, EI_PAD, LOADER_MARKER, + strlen(LOADER_MARKER)), 0); + ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj), + "loader.bpf.o"), 0); + + setenv("BINFMT_TEST_BINARY", LOADER_PATH, 1); + setenv("BINFMT_TEST_INTERP", LOADER_INTERP, 1); + + ASSERT_EQ(bpf_case_start(&c, self->obj, "loader", "test_bpf_loader"), 0); + status = run_payload(LOADER_PATH); + bpf_case_stop(&c); + EXPECT_EQ(status, 0); + + unsetenv("BINFMT_TEST_INTERP"); + unlink(LOADER_PATH); + unlink(LOADER_INTERP); +} + +/* The errno an exec of @path fails with, 0 if it succeeded. */ +static int exec_errno(const char *path) +{ + int status; + pid_t pid; + + pid = fork(); + if (pid == 0) { + execl(path, path, (char *)NULL); + _exit(errno); + } + if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +/* Install a copy of the bound-interpreter test binary at @path. */ +static int install_interp(const char *path) +{ + char src[PATH_MAX]; + + if (artifact_path(src, sizeof(src), "binfmt_bind_interp")) + return -1; + return copy_file(src, path); +} + +/* Bind @path to @entry under @name, the '+' command of a disabled entry. */ +static int entry_bind(const char *entry, const char *name, const char *path) +{ + char cmd[PATH_MAX]; + + snprintf(cmd, sizeof(cmd), "+%s %s\n", name, path); + return entry_command(entry, cmd); +} + +/* Set the interpreter budget of this namespace. */ +static int write_interp_limit(const char *val) +{ + ssize_t n; + int fd; + + fd = open(INTERP_LIMIT, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -1; + n = write(fd, val, strlen(val)); + close(fd); + return n < 0 ? -1 : 0; +} + +/* + * The errno a bind is refused with when the writer is a child that has spent + * the budget of a user namespace of its own, 0 if it succeeded and -1 if the + * child could not set itself up. The fd is opened here and inherited, so the + * interpreter is still opened with this process's credentials. + */ +static int bind_out_of_budget(const char *entry, const char *name, + const char *path) +{ + char cmd[PATH_MAX], file[PATH_MAX]; + int fd, status, retval; + pid_t pid; + + snprintf(file, sizeof(file), BINFMT_DIR "/%s", entry); + snprintf(cmd, sizeof(cmd), "+%s %s\n", name, path); + + fd = open(file, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -1; + + pid = fork(); + if (pid == 0) { + ssize_t n; + + /* A namespace of its own, with nothing left in it to spend. */ + if (unshare(CLONE_NEWUSER) || write_interp_limit("0")) + _exit(BIND_NO_BUDGET); + n = write(fd, cmd, strlen(cmd)); + _exit(n < 0 ? errno : 0); + } + close(fd); + if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + retval = WEXITSTATUS(status); + return retval == BIND_NO_BUDGET ? -1 : retval; +} + +FIXTURE(bound_interp) { + char obj[PATH_MAX]; + struct bpf_case c; + bool started; +}; + +FIXTURE_SETUP(bound_interp) +{ + const char *why = bpf_handler_unsupported(); + + if (why) + SKIP(return, "%s", why); + if (!binfmt_flag_supported('D')) { + ASSERT_EQ(errno, EINVAL); + SKIP(return, "kernel without the 'D' flag"); + } + + ASSERT_EQ(install_interp(BIND_FIRST), 0); + ASSERT_EQ(install_interp(BIND_SECOND), 0); + + ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj), + "interp_bind.bpf.o"), 0); + + /* + * Registered disabled, so it cannot be matched yet and can still be + * given interpreters. Each path is resolved once, by its write(2); + * from here on the entry holds the files themselves. + */ + ASSERT_EQ(bpf_case_start_flags(&self->c, self->obj, "interp_bind", + "test_interp_bind", "D"), 0); + self->started = true; + + ASSERT_EQ(entry_bind("test_interp_bind", "first", BIND_FIRST), 0); + ASSERT_EQ(entry_bind("test_interp_bind", "second", BIND_SECOND), 0); +} + +FIXTURE_TEARDOWN(bound_interp) +{ + if (self->started) + bpf_case_stop(&self->c); + unlink(BIND_FIRST); + unlink(BIND_SECOND); + unlink(AARCH64_PATH); + unlink(BIND_RISCV_PATH); + unlink(BIND_ARM_PATH); +} + +/* Enabling is what makes the configured entry matchable. */ +static int activate(const char *entry) +{ + return entry_command(entry, "1\n"); +} + +/* One entry, one interpreter per guest architecture, picked per exec. */ +TEST_F(bound_interp, selects_by_name) +{ + ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0); + ASSERT_EQ(create_fake_elf(BIND_RISCV_PATH, EM_RISCV), 0); + + /* Disabled, so it does not match and no format claims the binary. */ + EXPECT_EQ(exec_errno(AARCH64_PATH), ENOEXEC); + + ASSERT_EQ(activate("test_interp_bind"), 0); + EXPECT_EQ(check_output(AARCH64_PATH, BIND_EXPECT BIND_FIRST), 0); + EXPECT_EQ(check_output(BIND_RISCV_PATH, BIND_EXPECT BIND_SECOND), 0); +} + +/* What was bound is what runs, whatever the path holds afterwards. */ +TEST_F(bound_interp, path_no_longer_decides) +{ + char other[PATH_MAX]; + + ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0); + ASSERT_EQ(activate("test_interp_bind"), 0); + + /* Bound interpreters are pinned against writes, exactly like 'F'. */ + EXPECT_TRUE(write_denied(BIND_FIRST)); + + /* Replace the path with a different binary: a new file, new inode. */ + ASSERT_EQ(artifact_path(other, sizeof(other), "binfmt_bpf_interp"), 0); + ASSERT_EQ(unlink(BIND_FIRST), 0); + ASSERT_EQ(copy_file(other, BIND_FIRST), 0); + + EXPECT_EQ(check_output(AARCH64_PATH, BIND_EXPECT BIND_FIRST), 0); +} + +/* The entry reports what it bound, under the names it bound them as. */ +TEST_F(bound_interp, entry_reports_bindings) +{ + EXPECT_TRUE(entry_shows("test_interp_bind", + "bpf-interpreter first " BIND_FIRST)); + EXPECT_TRUE(entry_shows("test_interp_bind", + "bpf-interpreter second " BIND_SECOND)); +} + +/* Selecting a name the entry did not bind fails the exec. */ +TEST_F(bound_interp, unbound_name_fails) +{ + ASSERT_EQ(create_fake_elf(BIND_ARM_PATH, EM_ARM), 0); + ASSERT_EQ(activate("test_interp_bind"), 0); + + EXPECT_EQ(exec_errno(BIND_ARM_PATH), ENOENT); +} + +/* Activating seals it: what can be matched cannot be changed. */ +TEST_F(bound_interp, sealed_once_active) +{ + ASSERT_EQ(activate("test_interp_bind"), 0); + + EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_SECOND), -EBUSY); + EXPECT_FALSE(entry_shows("test_interp_bind", + "bpf-interpreter third " BIND_SECOND)); +} + +/* The seal is for good: disabling the entry again reopens nothing. */ +TEST_F(bound_interp, disable_does_not_unseal) +{ + ASSERT_EQ(activate("test_interp_bind"), 0); + ASSERT_EQ(entry_command("test_interp_bind", "0\n"), 0); + + EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_SECOND), -EBUSY); +} + +/* An entry registered without 'D' is sealed from the start. */ +TEST_F(bound_interp, born_sealed) +{ + /* A second entry for the handler the fixture already published. */ + ASSERT_EQ(register_entry("test_born_sealed", "interp_bind", NULL), 0); + + EXPECT_EQ(entry_bind("test_born_sealed", "first", BIND_FIRST), -EBUSY); + unregister("test_born_sealed"); +} + +/* A name is bound once; a second use of it is refused. */ +TEST_F(bound_interp, duplicate_name_refused) +{ + EXPECT_EQ(entry_bind("test_interp_bind", "first", BIND_SECOND), -EEXIST); +} + +/* A name is a printable word: the entry file reports 'name path' lines. */ +TEST_F(bound_interp, name_must_be_printable) +{ + /* A control character would forge a line into the entry file. */ + EXPECT_EQ(entry_bind("test_interp_bind", "a\tb", BIND_FIRST), -EINVAL); + EXPECT_EQ(entry_bind("test_interp_bind", "a\nb", BIND_FIRST), -EINVAL); + + /* A space cannot even be spelled: the path starts after the first one. */ + EXPECT_EQ(entry_bind("test_interp_bind", "a b", BIND_FIRST), -EINVAL); +} + +/* The command ends at the write: bytes past an embedded nul are refused. */ +TEST_F(bound_interp, trailing_bytes_refused) +{ + char cmd[PATH_MAX]; + size_t len; + int fd; + + /* entry_command() cannot spell a nul, so write the buffer raw. */ + snprintf(cmd, sizeof(cmd), "+nul %s", BIND_FIRST); + len = strlen(cmd) + 1; + memcpy(cmd + len, "junk", sizeof("junk")); + len += sizeof("junk"); + + fd = open(BINFMT_DIR "/test_interp_bind", O_WRONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + EXPECT_EQ(write(fd, cmd, len), -1); + EXPECT_EQ(errno, EINVAL); + close(fd); + + EXPECT_FALSE(entry_shows("test_interp_bind", + "bpf-interpreter nul " BIND_FIRST)); +} + +/* An entry binds at most BIND_MAX interpreters. */ +TEST_F(bound_interp, capped_bindings) +{ + char name[16]; + int i; + + /* The fixture bound "first" and "second" already. */ + for (i = 2; i < BIND_MAX; i++) { + snprintf(name, sizeof(name), "n%d", i); + ASSERT_EQ(entry_bind("test_interp_bind", name, BIND_FIRST), 0); + } + EXPECT_EQ(entry_bind("test_interp_bind", "over", BIND_FIRST), -ENOSPC); +} + +/* A binding pins a file: it is charged, and refused once the budget is out. */ +TEST_F(bound_interp, bindings_are_charged) +{ + int err = bind_out_of_budget("test_interp_bind", "third", BIND_FIRST); + + if (err < 0) + SKIP(return, "no user namespaces or no " INTERP_LIMIT); + + /* The charge follows the writer, not the entry file it writes to. */ + EXPECT_EQ(err, ENOSPC); + + /* The budget was the only thing in the way. */ + EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_FIRST), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/exec/binfmt_misc_common.h b/tools/testing/selftests/exec/binfmt_misc_common.h new file mode 100644 index 000000000000..745aff84dc78 --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_misc_common.h @@ -0,0 +1,315 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Helpers shared by the binfmt_misc selftests. */ +#ifndef __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H +#define __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H + +#include <elf.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <link.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#define BINFMT_DIR "/proc/sys/fs/binfmt_misc" +#define BINFMT_REG BINFMT_DIR "/register" + +/* comm holds 15 usable chars; a read of /proc/self/comm appends a newline. */ +#define TASK_COMM_LEN 16 + +/* The canonical payload argv: run_payload() passes it, the payloads assert it. */ +#define PAYLOAD_ARGV0 "payload-argv0" +#define PAYLOAD_ARG1 "argone" +#define PAYLOAD_ARG2 "argtwo" + +/* Marker the loader tests poke into the payload's e_ident padding. */ +#define LOADER_MARKER "LDRTST" + +/* Exit status run_payload() reports when the exec was refused as unhandled. */ +#define RUN_ENOEXEC 42 + +static inline int copy_file(const char *src, const char *dst) +{ + char buf[4096]; + int in, out; + ssize_t n; + + in = open(src, O_RDONLY); + if (in < 0) + return -1; + /* The tests share /tmp, so never write through a name they don't own. */ + unlink(dst); + out = open(dst, O_WRONLY | O_CREAT | O_EXCL, 0755); + if (out < 0) { + close(in); + return -1; + } + while ((n = read(in, buf, sizeof(buf))) > 0) { + if (write(out, buf, n) != n) { + close(in); + close(out); + return -1; + } + } + close(in); + close(out); + return n < 0 ? -1 : 0; +} + +/* Write @rule to the register file, preserving the write's errno. */ +static inline int write_reg(const char *rule) +{ + int fd, saved; + ssize_t n; + + fd = open(BINFMT_REG, O_WRONLY); + if (fd < 0) + return -1; + n = write(fd, rule, strlen(rule)); + saved = errno; + close(fd); + errno = saved; + return n < 0 ? -1 : 0; +} + +static inline void unregister(const char *name) +{ + char path[PATH_MAX]; + int fd; + + snprintf(path, sizeof(path), BINFMT_DIR "/%s", name); + fd = open(path, O_WRONLY); + if (fd >= 0) { + if (write(fd, "-1", 2) < 0) + ; /* best effort */ + close(fd); + } +} + +/* Write @line to @entry's file, reporting the errno it was refused with. */ +static inline int entry_command(const char *entry, const char *line) +{ + char path[PATH_MAX]; + int fd, retval = 0; + size_t len = strlen(line); + + snprintf(path, sizeof(path), BINFMT_DIR "/%s", entry); + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -errno; + if (write(fd, line, len) != (ssize_t)len) + retval = -errno; + close(fd); + return retval; +} + +/* Does @entry's file report @line? */ +static inline bool entry_shows(const char *entry, const char *line) +{ + char path[PATH_MAX], buf[PATH_MAX]; + bool found = false; + FILE *fp; + + snprintf(path, sizeof(path), BINFMT_DIR "/%s", entry); + fp = fopen(path, "r"); + if (!fp) + return false; + while (fgets(buf, sizeof(buf), fp)) { + buf[strcspn(buf, "\n")] = '\0'; + if (!strcmp(buf, line)) { + found = true; + break; + } + } + fclose(fp); + return found; +} + +/* Mount binfmt_misc unless it already is, and report whether it is usable. */ +static inline bool binfmt_misc_available(void) +{ + if (access(BINFMT_REG, F_OK) < 0) + mount("binfmt_misc", BINFMT_DIR, "binfmt_misc", 0, NULL); + return access(BINFMT_REG, F_OK) == 0; +} + +/* Absolute path of @name in the directory this test was built into. */ +static inline int artifact_path(char *out, size_t sz, const char *name) +{ + char exe[PATH_MAX]; + ssize_t n; + + n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); + if (n < 0) + return -1; + exe[n] = '\0'; + if ((size_t)snprintf(out, sz, "%s/%s", dirname(exe), name) >= sz) + return -1; + return 0; +} + +/* Probe kernel support for a registration flag with a throwaway entry. */ +static inline bool binfmt_flag_supported(char flag) +{ + char rule[64]; + + snprintf(rule, sizeof(rule), ":bm_flag_probe:E::bmprobe::/bin/true:%c", + flag); + if (write_reg(rule)) + return false; + unregister("bm_flag_probe"); + return true; +} + +/* + * Run @path with the canonical payload argv and return its exit status, or + * RUN_ENOEXEC when the exec itself was refused as unhandled. + */ +static inline int run_payload(const char *path) +{ + int status; + pid_t pid; + + pid = fork(); + if (pid == 0) { + execl(path, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, + (char *)NULL); + _exit(errno == ENOEXEC ? RUN_ENOEXEC : 126); + } + if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +/* Does the exe link name @path? */ +static inline bool exe_is(const char *path) +{ + char exe[PATH_MAX], real[PATH_MAX]; + ssize_t n; + + n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); + if (n <= 0 || !realpath(path, real)) + return false; + exe[n] = '\0'; + return !strcmp(exe, real); +} + +/* Is comm @name truncated to what a comm can hold? */ +static inline bool comm_is(const char *name) +{ + char comm[TASK_COMM_LEN + 2], expect[TASK_COMM_LEN]; + ssize_t n; + int fd; + + fd = open("/proc/self/comm", O_RDONLY); + if (fd < 0) + return false; + n = read(fd, comm, sizeof(comm) - 1); + close(fd); + if (n <= 0) + return false; + if (comm[n - 1] == '\n') + n--; + comm[n] = '\0'; + snprintf(expect, sizeof(expect), "%s", name); + return !strcmp(comm, expect); +} + +/* Opening @path for writing has to fail with ETXTBSY. */ +static inline bool write_denied(const char *path) +{ + int fd = open(path, O_WRONLY); + + if (fd >= 0) { + close(fd); + return false; + } + return errno == ETXTBSY; +} + +static inline int patch_file(const char *path, off_t off, const void *data, size_t len) +{ + ssize_t n; + int fd; + + fd = open(path, O_WRONLY); + if (fd < 0) + return -1; + n = pwrite(fd, data, len, off); + close(fd); + return n == (ssize_t)len ? 0 : -1; +} + +/* start_code and end_code are the 26th and 27th fields of /proc/pid/stat. */ +static inline int stat_codes(pid_t pid, unsigned long *start_code, + unsigned long *end_code) +{ + char buf[4096], path[64], *p; + ssize_t n; + int fd, i; + + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + n = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (n <= 0) + return -1; + buf[n] = '\0'; + + /* Skip "pid (comm)", then start_code is the 24th field after it. */ + p = strrchr(buf, ')'); + if (!p) + return -1; + p++; + for (i = 0; i < 23; i++) { + p = strchr(p + 1, ' '); + if (!p) + return -1; + } + if (sscanf(p, " %lu %lu", start_code, end_code) != 2) + return -1; + return 0; +} + +/* Find the system loader through our own PT_INTERP. */ +static inline int find_loader(char *out, size_t sz) +{ + ElfW(Ehdr) eh; + ElfW(Phdr) ph; + int fd, i, ret = -1; + + fd = open("/proc/self/exe", O_RDONLY); + if (fd < 0) + return -1; + if (pread(fd, &eh, sizeof(eh), 0) != sizeof(eh)) + goto out; + for (i = 0; i < eh.e_phnum; i++) { + if (pread(fd, &ph, sizeof(ph), + eh.e_phoff + i * eh.e_phentsize) != sizeof(ph)) + goto out; + if (ph.p_type != PT_INTERP) + continue; + if (!ph.p_filesz || ph.p_filesz > sz) + goto out; + if (pread(fd, out, ph.p_filesz, ph.p_offset) != + (ssize_t)ph.p_filesz) + goto out; + out[ph.p_filesz - 1] = '\0'; + ret = 0; + break; + } +out: + close(fd); + return ret; +} + +#endif /* __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H */ diff --git a/tools/testing/selftests/exec/binfmt_misc_disabled.c b/tools/testing/selftests/exec/binfmt_misc_disabled.c new file mode 100644 index 000000000000..47c9e8a4ee42 --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_misc_disabled.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test the 'D' (register disabled) flag of binfmt_misc. An entry + * registered with it exists but cannot be matched until userspace enables + * it, which splits a registration into create and activate. + * + * Needs root for the registration; no bpf toolchain involved. + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> + +#include "binfmt_misc_common.h" +#include "kselftest_harness.h" + +#define MAGIC "#DISABLED-SELFTEST#" +#define TARGET_PATH "/tmp/binfmt_disabled_target" +#define INTERP_PATH "/tmp/binfmt_disabled_interp.sh" +#define ENTRY "test_disabled" +#define RULE(flags) ":" ENTRY ":M:0:" MAGIC "::" INTERP_PATH ":" flags + +/* The interpreter exits with a code the harness can recognise. */ +#define EXIT_INTERP 7 + +/* The target only has to carry the magic; it is never actually loaded. */ +static int create_target(void) +{ + char buf[128] = MAGIC "\n"; + int fd; + + unlink(TARGET_PATH); + fd = open(TARGET_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755); + if (fd < 0) + return -1; + if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) { + close(fd); + return -1; + } + close(fd); + return 0; +} + +static int create_interp(void) +{ + char buf[64]; + int fd; + + unlink(INTERP_PATH); + fd = open(INTERP_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755); + if (fd < 0) + return -1; + snprintf(buf, sizeof(buf), "#!/bin/sh\nexit %d\n", EXIT_INTERP); + if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) { + close(fd); + return -1; + } + return close(fd); +} + +FIXTURE(disabled) { +}; + +FIXTURE_SETUP(disabled) +{ + if (getuid() != 0) + SKIP(return, "test must be run as root"); + if (!binfmt_misc_available()) + SKIP(return, "no binfmt_misc"); + + /* Skip the whole suite on a kernel that does not know 'D'. */ + if (!binfmt_flag_supported('D')) { + ASSERT_EQ(errno, EINVAL); + SKIP(return, "kernel without the 'D' flag"); + } + + ASSERT_EQ(create_interp(), 0); + ASSERT_EQ(create_target(), 0); +} + +FIXTURE_TEARDOWN(disabled) +{ + unregister(ENTRY); + unlink(TARGET_PATH); + unlink(INTERP_PATH); +} + +/* The entry exists but does not dispatch until it is enabled. */ +TEST_F(disabled, inert_until_enabled) +{ + ASSERT_EQ(write_reg(RULE("D")), 0); + EXPECT_TRUE(entry_shows(ENTRY, "disabled")); + + /* Nothing matches it, so no binary format claims the target. */ + EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC); + + ASSERT_EQ(entry_command(ENTRY, "1\n"), 0); + EXPECT_TRUE(entry_shows(ENTRY, "enabled")); + EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP); +} + +/* Without 'D' an entry is matchable the moment it is registered. */ +TEST_F(disabled, enabled_without_the_flag) +{ + ASSERT_EQ(write_reg(RULE("")), 0); + EXPECT_TRUE(entry_shows(ENTRY, "enabled")); + EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP); +} + +/* 'D' is spent on the registration: the entry does not report it back. */ +TEST_F(disabled, flag_not_reported) +{ + ASSERT_EQ(write_reg(RULE("D")), 0); + EXPECT_FALSE(entry_shows(ENTRY, "flags: D")); + EXPECT_TRUE(entry_shows(ENTRY, "flags: ")); +} + +/* A disabled entry can be disabled and enabled like any other. */ +TEST_F(disabled, toggles_like_any_entry) +{ + ASSERT_EQ(write_reg(RULE("D")), 0); + + ASSERT_EQ(entry_command(ENTRY, "1\n"), 0); + ASSERT_EQ(run_payload(TARGET_PATH), EXIT_INTERP); + ASSERT_EQ(entry_command(ENTRY, "0\n"), 0); + EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC); + ASSERT_EQ(entry_command(ENTRY, "1\n"), 0); + EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP); +} + +/* 'D' composes with the invocation flags a static entry can carry. */ +TEST_F(disabled, composes_with_invocation_flags) +{ + ASSERT_EQ(write_reg(RULE("PD")), 0); + EXPECT_TRUE(entry_shows(ENTRY, "disabled")); + EXPECT_TRUE(entry_shows(ENTRY, "flags: P")); +} + +/* '-1' to the status file sweeps a staged entry with everything else. */ +TEST_F(disabled, removed_by_remove_all) +{ + int fd; + + ASSERT_EQ(write_reg(RULE("D")), 0); + EXPECT_TRUE(entry_shows(ENTRY, "disabled")); + + fd = open(BINFMT_DIR "/status", O_WRONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_EQ(write(fd, "-1", 2), 2); + close(fd); + + EXPECT_NE(access(BINFMT_DIR "/" ENTRY, F_OK), 0); +} + +/* A file handle held across a removal cannot resurrect the entry. */ +TEST_F(disabled, no_resurrection_after_remove) +{ + int fd; + + ASSERT_EQ(write_reg(RULE("D")), 0); + fd = open(BINFMT_DIR "/" ENTRY, O_WRONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + + ASSERT_EQ(write(fd, "-1", 2), 2); + EXPECT_NE(access(BINFMT_DIR "/" ENTRY, F_OK), 0); + + /* Accepted like any toggle of a removed entry, but publishes nothing. */ + EXPECT_EQ(write(fd, "1", 1), 1); + EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC); + close(fd); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/exec/binfmt_misc_interplimit.c b/tools/testing/selftests/exec/binfmt_misc_interplimit.c new file mode 100644 index 000000000000..bf611c551784 --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_misc_interplimit.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A pre-opened interpreter - what 'F' gives a static entry and what a 'B' + * entry binds - keeps a file open for as long as the entry lives, so it pins + * the mount it came from. It costs no file descriptor, and binfmt_misc is + * FS_USERNS_MOUNT, so an unprivileged user namespace can create them without + * bound. Check that UCOUNT_BINFMT_MISC_INTERPRETERS bounds it, that an entry + * that pre-opens nothing is not charged, that removing an entry gives the + * charge back, and that nesting a user namespace does not evade it. + * + * Runs unprivileged in a user namespace. + */ +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "../filesystems/utils.h" +#include "kselftest_harness.h" + +#define MNT "/tmp/binfmt_interplimit" +#define NESTED_MNT "/tmp/binfmt_interplimit_nested" +#define LIMIT_SYSCTL "/proc/sys/user/max_binfmt_misc_interpreters" + +#define MAGIC "\\xde\\xad" +/* Not on the instance, and unlike /bin/true it always exists. */ +#define INTERP "/proc/self/exe" + +/* Small enough to fill by hand, big enough that a refund is visible. */ +#define LIMIT 4 + +/* What UCOUNT_ENTRY() lets a namespace raise its own limit to. */ +#define LIMIT_MAX "2147483647" + +static int ensure_dir(const char *path) +{ + if (mkdir(path, 0755) && errno != EEXIST) + return -1; + return 0; +} + +/* Write @val to @path, preserving write(2)'s errno for the caller. */ +static int write_keep_errno(const char *path, const char *val) +{ + int fd, saved; + ssize_t n; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -1; + n = write(fd, val, strlen(val)); + saved = errno; + close(fd); + errno = saved; + return n < 0 ? -1 : 0; +} + +static int set_limit(const char *val) +{ + return write_keep_errno(LIMIT_SYSCTL, val); +} + +static int register_at(const char *mnt, const char *rule) +{ + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "%s/register", mnt); + return write_keep_errno(path, rule); +} + +/* An 'F' entry: one interpreter pre-opened at registration, one charge. */ +static int register_fixed(const char *mnt, const char *name) +{ + char rule[PATH_MAX]; + + snprintf(rule, sizeof(rule), ":%s:M::" MAGIC "::" INTERP ":F", name); + return register_at(mnt, rule); +} + +/* The same entry without 'F': the interpreter is opened per exec instead. */ +static int register_plain(const char *mnt, const char *name) +{ + char rule[PATH_MAX]; + + snprintf(rule, sizeof(rule), ":%s:M::" MAGIC "::" INTERP ":", name); + return register_at(mnt, rule); +} + +static int remove_entry(const char *mnt, const char *name) +{ + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "%s/%s", mnt, name); + return write_keep_errno(path, "-1\n"); +} + +static bool entry_exists(const char *mnt, const char *name) +{ + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "%s/%s", mnt, name); + return access(path, F_OK) == 0; +} + +/* Register @n 'F' entries, each with a name of its own. */ +static int fill_budget(const char *mnt, unsigned int n) +{ + char name[32]; + unsigned int i; + + for (i = 0; i < n; i++) { + snprintf(name, sizeof(name), "fixed%u", i); + if (register_fixed(mnt, name)) + return -1; + } + return 0; +} + +FIXTURE(interp_limit) { +}; + +FIXTURE_SETUP(interp_limit) +{ + /* setup_userns() exits rather than returns if this is not there. */ + if (access("/proc/self/ns/user", F_OK)) + SKIP(return, "kernel without user namespaces"); + ASSERT_EQ(setup_userns(), 0); + + /* CAP_SYS_RESOURCE in this namespace is what makes it writable. */ + if (set_limit(LIMIT_MAX)) { + if (errno == ENOENT) + SKIP(return, "kernel without " LIMIT_SYSCTL); + SKIP(return, "cannot set the limit: %s", strerror(errno)); + } + + ASSERT_EQ(ensure_dir(MNT), 0); + if (mount("binfmt_misc", MNT, "binfmt_misc", 0, NULL)) { + int saved = errno; + + /* Teardown doesn't run when setup skips, so clean up here. */ + rmdir(MNT); + SKIP(return, "no binfmt_misc: %s", strerror(saved)); + } +} + +FIXTURE_TEARDOWN(interp_limit) +{ + /* The namespaces go with the process; just don't litter /tmp. */ + umount2(NESTED_MNT, MNT_DETACH); + umount2(MNT, MNT_DETACH); + rmdir(NESTED_MNT); + rmdir(MNT); +} + +/* Every pre-opened interpreter is charged, and the budget is a hard stop. */ +TEST_F(interp_limit, fixed_interpreters_are_charged) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%u", LIMIT); + ASSERT_EQ(set_limit(buf), 0); + + ASSERT_EQ(fill_budget(MNT, LIMIT), 0); + + EXPECT_NE(register_fixed(MNT, "over"), 0); + EXPECT_EQ(errno, ENOSPC); + + /* A refused registration leaves nothing behind. */ + EXPECT_FALSE(entry_exists(MNT, "over")); +} + +/* An entry that pre-opens nothing pins nothing, so it is not charged. */ +TEST_F(interp_limit, plain_entries_are_not_charged) +{ + ASSERT_EQ(set_limit("0"), 0); + + EXPECT_EQ(register_plain(MNT, "plain"), 0); + EXPECT_TRUE(entry_exists(MNT, "plain")); + + /* ... while the same entry with 'F' has nothing to spend. */ + EXPECT_NE(register_fixed(MNT, "fixed"), 0); + EXPECT_EQ(errno, ENOSPC); +} + +/* Removing an entry closes its interpreters and gives the charge back. */ +TEST_F(interp_limit, removal_refunds_the_charge) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%u", LIMIT); + ASSERT_EQ(set_limit(buf), 0); + + ASSERT_EQ(fill_budget(MNT, LIMIT), 0); + ASSERT_NE(register_fixed(MNT, "over"), 0); + + ASSERT_EQ(remove_entry(MNT, "fixed0"), 0); + EXPECT_EQ(register_fixed(MNT, "over"), 0); +} + +/* + * The charge walks the ancestors, so a namespace cannot buy itself budget by + * nesting: it may raise only its own limit, and the parent it was created + * from is charged for every binding made below it. + */ +TEST_F(interp_limit, nesting_does_not_evade_it) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%u", LIMIT); + ASSERT_EQ(set_limit(buf), 0); + ASSERT_EQ(fill_budget(MNT, LIMIT), 0); + + ASSERT_EQ(setup_userns(), 0); + ASSERT_EQ(set_limit(LIMIT_MAX), 0); + + ASSERT_EQ(ensure_dir(NESTED_MNT), 0); + ASSERT_EQ(mount("binfmt_misc", NESTED_MNT, "binfmt_misc", 0, NULL), 0); + + /* A fresh instance with an unlimited budget of its own, and yet: */ + EXPECT_NE(register_fixed(NESTED_MNT, "nested"), 0); + EXPECT_EQ(errno, ENOSPC); + + /* The nested instance works for anything that pins no file. */ + EXPECT_EQ(register_plain(NESTED_MNT, "nested_plain"), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/exec/binfmt_misc_loader.c b/tools/testing/selftests/exec/binfmt_misc_loader.c new file mode 100644 index 000000000000..1e14dcd274af --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_misc_loader.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test the 'L' (loader substitution) flag of binfmt_misc. A matched + * binary runs as the MAIN image - a fully native exec - with the + * registered interpreter substituted for its PT_INTERP. The payload + * (binfmt_loader_payload) asserts the native identity from inside. + * + * The substitute is a copy of the system loader found via our own + * PT_INTERP; magic matching pokes a marker into the ELF header's + * e_ident padding, which kernel and loader ignore. + * + * Needs root for the registration; no bpf toolchain involved. + */ +#define _GNU_SOURCE +#include <elf.h> +#include <link.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/ptrace.h> +#include <sys/syscall.h> +#include <sys/wait.h> + +#include "binfmt_misc_common.h" +#include "kselftest_harness.h" + +#define ENTRY "test_loader" +#define INTERP_PATH "/tmp/binfmt_loader_interp" +#define MOVED_PATH INTERP_PATH ".moved" +#define TARGET_PATH "/tmp/binfmt_loader_target.ldrtest" +#define STATIC_PATH "/tmp/binfmt_loader_static.ldrtest" +#define FOREIGN_PATH "/tmp/binfmt_loader_foreign.ldrtest" +#define SCRIPT_PATH "/tmp/binfmt_loader_script.ldrtest" +#define M_RULE ":" ENTRY ":M:9:" LOADER_MARKER "::" INTERP_PATH ":L" +#define E_RULE ":" ENTRY ":E::ldrtest::" INTERP_PATH ":L" +#define FL_RULE ":" ENTRY ":E::ldrtest::" INTERP_PATH ":FL" + +/* Execute the binary from an inaccessible O_CLOEXEC memfd. */ +static int run_memfd(const char *path) +{ + int status; + pid_t pid; + + pid = fork(); + if (pid == 0) { + char *argv[] = { PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, NULL }; + char buf[4096]; + int in, mfd; + ssize_t n; + + mfd = memfd_create("loader-test", MFD_CLOEXEC); + in = open(path, O_RDONLY); + if (mfd < 0 || in < 0) + _exit(125); + while ((n = read(in, buf, sizeof(buf))) > 0) + if (write(mfd, buf, n) != n) + _exit(125); + close(in); + setenv("BINFMT_TEST_MEMFD", "1", 1); + unsetenv("BINFMT_TEST_BINARY"); + syscall(SYS_execveat, mfd, "", argv, environ, AT_EMPTY_PATH); + _exit(126); + } + if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +/* + * The differentiator against the transparent mode: at PTRACE_EVENT_EXEC + * the identity is already complete - exe, auxv and the stat code markers + * are mutually consistent with no window a debugger could observe. + */ +static int ptrace_probe(const char *target) +{ + unsigned long auxv[2 * 64], base = 0, entry = 0, at_flags = 0; + unsigned long start_code = 0, end_code = 0; + int status, fd, execfd_seen = 0, failed = 0; + char path[64], buf[PATH_MAX]; + ssize_t n; + pid_t pid; + int i; + + pid = fork(); + if (pid == 0) { + ptrace(PTRACE_TRACEME, 0, NULL, NULL); + raise(SIGSTOP); + execl(target, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, (char *)NULL); + _exit(126); + } + if (pid < 0) + return -1; + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status)) + goto fail_kill; + if (ptrace(PTRACE_SETOPTIONS, pid, NULL, (void *)PTRACE_O_TRACEEXEC)) + goto fail_kill; + if (ptrace(PTRACE_CONT, pid, NULL, NULL)) + goto fail_kill; + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) || + status >> 8 != (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) { + fprintf(stderr, "no exec stop (status %#x)\n", status); + goto fail_kill; + } + + snprintf(path, sizeof(path), "/proc/%d/exe", pid); + n = readlink(path, buf, sizeof(buf) - 1); + if (n <= 0) { + failed = 1; + } else { + buf[n] = '\0'; + if (strcmp(buf, target)) { + fprintf(stderr, "exe at exec stop: %s\n", buf); + failed = 1; + } + } + + snprintf(path, sizeof(path), "/proc/%d/auxv", pid); + fd = open(path, O_RDONLY); + if (fd < 0) { + n = -1; + } else { + n = read(fd, auxv, sizeof(auxv)); + close(fd); + } + if (n <= 0) { + failed = 1; + n = 0; + } + for (i = 0; i + 1 < (int)(n / sizeof(unsigned long)); i += 2) { + switch (auxv[i]) { + case AT_BASE: + base = auxv[i + 1]; + break; + case AT_ENTRY: + entry = auxv[i + 1]; + break; + case AT_FLAGS: + at_flags = auxv[i + 1]; + break; + case AT_EXECFD: + execfd_seen = 1; + break; + } + } + + if (stat_codes(pid, &start_code, &end_code)) + failed = 1; + + if (!base || execfd_seen || at_flags) { + fprintf(stderr, "auxv at exec stop not native\n"); + failed = 1; + } + if (!start_code || entry < start_code || entry >= end_code) { + fprintf(stderr, "auxv/stat inconsistent at exec stop\n"); + failed = 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL)) + goto fail_kill; + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) || + WEXITSTATUS(status)) + failed = 1; + return failed ? -1 : 0; + +fail_kill: + kill(pid, SIGKILL); + waitpid(pid, &status, 0); + return -1; +} + +FIXTURE(loader) { + bool have_static; +}; + +FIXTURE_SETUP(loader) +{ + unsigned short foreign_machine = 0xdead; + char src[PATH_MAX], loader[PATH_MAX]; + + if (getuid() != 0) + SKIP(return, "test must be run as root"); + if (!binfmt_misc_available()) + SKIP(return, "no binfmt_misc"); + if (find_loader(loader, sizeof(loader))) + SKIP(return, "cannot determine own PT_INTERP"); + + ASSERT_EQ(copy_file(loader, INTERP_PATH), 0); + + ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0); + ASSERT_EQ(copy_file(src, TARGET_PATH), 0); + ASSERT_EQ(patch_file(TARGET_PATH, EI_PAD, LOADER_MARKER, + strlen(LOADER_MARKER)), 0); + + /* The same payload with a machine type this kernel cannot load. */ + ASSERT_EQ(copy_file(src, FOREIGN_PATH), 0); + ASSERT_EQ(patch_file(FOREIGN_PATH, EI_PAD, LOADER_MARKER, + strlen(LOADER_MARKER)), 0); + ASSERT_EQ(patch_file(FOREIGN_PATH, offsetof(ElfW(Ehdr), e_machine), + &foreign_machine, sizeof(foreign_machine)), 0); + + self->have_static = + artifact_path(src, sizeof(src), "binfmt_loader_payload_static") == 0 && + copy_file(src, STATIC_PATH) == 0; + + setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1); + setenv("BINFMT_TEST_INTERP", INTERP_PATH, 1); + + /* Everything below needs the flag; find out once. */ + if (write_reg(E_RULE)) { + ASSERT_EQ(errno, EINVAL); + SKIP(return, "kernel without the 'L' flag"); + } + unregister(ENTRY); +} + +FIXTURE_TEARDOWN(loader) +{ + unregister(ENTRY); + if (access(MOVED_PATH, F_OK) == 0) + rename(MOVED_PATH, INTERP_PATH); + unlink(TARGET_PATH); + unlink(STATIC_PATH); + unlink(FOREIGN_PATH); + unlink(SCRIPT_PATH); + unlink(INTERP_PATH); +} + +/* Grammar sanity check: the same entry without 'L' has to register. */ +TEST_F(loader, plain_entry_registers) +{ + ASSERT_EQ(write_reg(":" ENTRY ":E::ldrtest::" INTERP_PATH ":"), 0); +} + +/* 'L' is a native exec: every classic-dispatch flag is rejected. */ +TEST_F(loader, rejects_classic_flags) +{ + static const char * const combos[] = { "LT", "LP", "LC", "LO" }; + char rule[PATH_MAX]; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(combos); i++) { + int rc; + + snprintf(rule, sizeof(rule), + ":" ENTRY ":E::ldrtest::" INTERP_PATH ":%s", combos[i]); + rc = write_reg(rule); + EXPECT_EQ(rc, -1) + TH_LOG("'%s' was not rejected", combos[i]); + if (rc == 0) { + unregister(ENTRY); + continue; + } + EXPECT_EQ(errno, EINVAL); + } +} + +/* + * Without 'F' the interpreter is opened when the binary is executed, so a + * relative path would be resolved against the caller's working directory. + */ +TEST_F(loader, rejects_relative_interpreter) +{ + static const char * const flags[] = { "L", "C" }; + char rule[PATH_MAX]; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(flags); i++) { + int rc; + + snprintf(rule, sizeof(rule), + ":" ENTRY ":E::ldrtest::binfmt_loader_interp:%s", + flags[i]); + rc = write_reg(rule); + EXPECT_EQ(rc, -1) + TH_LOG("'%s' accepted a relative interpreter", flags[i]); + if (rc == 0) { + unregister(ENTRY); + continue; + } + EXPECT_EQ(errno, EINVAL); + } +} + +TEST_F(loader, extension_matched) +{ + ASSERT_EQ(write_reg(E_RULE), 0); + EXPECT_EQ(run_payload(TARGET_PATH), 0); +} + +TEST_F(loader, magic_matched) +{ + ASSERT_EQ(write_reg(M_RULE), 0); + EXPECT_EQ(run_payload(TARGET_PATH), 0); +} + +/* + * The differentiator against the transparent mode: at PTRACE_EVENT_EXEC the + * identity is already complete, with no window a debugger could observe. + */ +TEST_F(loader, exec_stop_consistency) +{ + ASSERT_EQ(write_reg(E_RULE), 0); + EXPECT_EQ(ptrace_probe(TARGET_PATH), 0); +} + +/* A binary without PT_INTERP drops the override and runs natively. */ +TEST_F(loader, static_binary_runs_natively) +{ + if (!self->have_static) + SKIP(return, "no static payload built"); + + ASSERT_EQ(write_reg(E_RULE), 0); + setenv("BINFMT_TEST_BINARY", STATIC_PATH, 1); + setenv("BINFMT_TEST_STATIC", "1", 1); + EXPECT_EQ(run_payload(STATIC_PATH), 0); + unsetenv("BINFMT_TEST_STATIC"); + setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1); +} + +/* + * A '#!' file that matched an 'L' entry is claimed by binfmt_script, which + * sits ahead of binfmt_elf. The substitute the entry staged has to be + * released when the interpreter replaces the file, not leaked. + */ +TEST_F(loader, script_claims_the_file) +{ + static const char script[] = "#!/bin/sh\nexit 0\n"; + int fd; + + unlink(SCRIPT_PATH); + fd = open(SCRIPT_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755); + ASSERT_GE(fd, 0); + ASSERT_EQ(write(fd, script, sizeof(script) - 1), + (ssize_t)sizeof(script) - 1); + ASSERT_EQ(close(fd), 0); + + ASSERT_EQ(write_reg(E_RULE), 0); + EXPECT_EQ(run_payload(SCRIPT_PATH), 0); + + /* A leaked substitute keeps its write denial on the loader. */ + fd = open(INTERP_PATH, O_WRONLY); + EXPECT_GE(fd, 0) + TH_LOG("loader still write denied (errno %d)", errno); + if (fd >= 0) + close(fd); +} + +/* Nothing needs the binary's path, so an inaccessible fd works. */ +TEST_F(loader, inaccessible_memfd) +{ + ASSERT_EQ(write_reg(M_RULE), 0); + EXPECT_EQ(run_memfd(TARGET_PATH), 0); +} + +/* The whole exec of a wrong-arch binary fails as if unhandled. */ +TEST_F(loader, foreign_arch_enoexec) +{ + ASSERT_EQ(write_reg(M_RULE), 0); + EXPECT_EQ(run_payload(FOREIGN_PATH), RUN_ENOEXEC); +} + +/* 'F' pre-opens the substitute, so it survives losing its path. */ +TEST_F(loader, fixed_interpreter_survives_rename) +{ + ASSERT_EQ(write_reg(FL_RULE), 0); + ASSERT_EQ(rename(INTERP_PATH, MOVED_PATH), 0); + EXPECT_EQ(run_payload(TARGET_PATH), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/exec/binfmt_misc_selfpin.c b/tools/testing/selftests/exec/binfmt_misc_selfpin.c new file mode 100644 index 000000000000..5286b0604eed --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_misc_selfpin.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An 'F' entry keeps its interpreter open for as long as the entry exists, + * and the entry only goes away when the binfmt_misc superblock is destroyed. + * An interpreter that lives on a mount which in turn keeps that superblock + * alive therefore pins the instance that owns it, and nothing can break the + * cycle. Check the two ways userspace could arrange for that: an interpreter + * on the binfmt_misc instance itself, and one on a filesystem stacked on it. + * + * Runs unprivileged in a user namespace; binfmt_misc is FS_USERNS_MOUNT. + */ +#define _GNU_SOURCE +#include <fcntl.h> +#include <limits.h> +#include <sched.h> +#include <sys/mount.h> +#include <sys/stat.h> + +#include "../filesystems/utils.h" +#include "kselftest_harness.h" + +#define MNT "/tmp/binfmt_selfpin" +#define BACKING "/tmp/binfmt_selfpin_back" +#define LOWER BACKING "/lower" +#define MERGED "/tmp/binfmt_selfpin_merged" + +#define MAGIC "\\xde\\xad" +#define RULE(interp) ":selfpin:M::" MAGIC "::" interp ":F" +/* Not on the instance, and unlike /bin/true it always exists. */ +#define INTERP "/proc/self/exe" + +#define OPTS_MAX (3 * PATH_MAX + 64) + +static int ensure_dir(const char *path) +{ + if (mkdir(path, 0755) && errno != EEXIST) + return -1; + return 0; +} + +/* Write @rule to this instance's register file, preserving write(2)'s errno. */ +static int register_at(struct __test_metadata *_metadata, const char *rule) +{ + int fd, saved; + ssize_t n; + + fd = open(MNT "/register", O_WRONLY); + ASSERT_GE(fd, 0); + n = write(fd, rule, strlen(rule)); + saved = errno; + close(fd); + errno = saved; + return n < 0 ? -1 : 0; +} + +/* + * Mount an overlay over @lower using a private upper/work pair, so the two + * mounts this test performs cannot interfere with each other and neither + * overlaps the lower layer. + */ +static int mount_overlay(const char *lower, int nr) +{ + char opts[OPTS_MAX], upper[PATH_MAX], work[PATH_MAX]; + + snprintf(upper, sizeof(upper), "%s/upper%d", BACKING, nr); + snprintf(work, sizeof(work), "%s/work%d", BACKING, nr); + if (mkdir(upper, 0755) || mkdir(work, 0755)) + return -1; + + snprintf(opts, sizeof(opts), "lowerdir=%s,upperdir=%s,workdir=%s", + lower, upper, work); + return mount("ovl", MERGED, "overlay", 0, opts); +} + +FIXTURE(selfpin) { +}; + +FIXTURE_SETUP(selfpin) +{ + /* setup_userns() exits rather than returns if this is not there. */ + if (access("/proc/self/ns/user", F_OK)) + SKIP(return, "kernel without user namespaces"); + ASSERT_EQ(setup_userns(), 0); + + ASSERT_EQ(ensure_dir(MNT), 0); + if (mount("binfmt_misc", MNT, "binfmt_misc", 0, NULL)) { + int saved = errno; + + /* Teardown doesn't run when setup skips, so clean up here. */ + rmdir(MNT); + SKIP(return, "no binfmt_misc: %s", strerror(saved)); + } +} + +FIXTURE_TEARDOWN(selfpin) +{ + /* The namespaces go with the process; just don't litter /tmp. */ + umount2(MERGED, MNT_DETACH); + umount2(BACKING, MNT_DETACH); + umount2(MNT, MNT_DETACH); + rmdir(MERGED); + rmdir(BACKING); + rmdir(MNT); +} + +/* + * The instance's own files are regular files the mounter owns, so they can be + * made executable. Opening one for exec still has to fail, otherwise the entry + * pins the very superblock it lives in. + */ +TEST_F(selfpin, interpreter_on_the_instance) +{ + ASSERT_EQ(chmod(MNT "/status", 0755), 0); + + ASSERT_NE(register_at(_metadata, RULE(MNT "/status")), 0); + EXPECT_EQ(errno, EACCES); +} + +/* Same for an entry file rather than one of the control files. */ +TEST_F(selfpin, interpreter_on_an_entry) +{ + ASSERT_EQ(register_at(_metadata, ":victim:M::" MAGIC "::" INTERP ":"), 0); + ASSERT_EQ(chmod(MNT "/victim", 0755), 0); + + ASSERT_NE(register_at(_metadata, RULE(MNT "/victim")), 0); + EXPECT_EQ(errno, EACCES); +} + +/* + * A stacking filesystem holds a private clone of each layer for its whole + * lifetime, so an instance used as a layer can be pinned by an interpreter + * that does not live on it at all. Refuse to be a layer. + */ +TEST_F(selfpin, refuses_to_be_stacked_on) +{ + ASSERT_EQ(ensure_dir(BACKING), 0); + ASSERT_EQ(mount("tmpfs", BACKING, "tmpfs", 0, NULL), 0); + ASSERT_EQ(mkdir(LOWER, 0755), 0); + ASSERT_EQ(ensure_dir(MERGED), 0); + + /* Nothing to prove unless overlayfs works here at all. */ + if (mount_overlay(LOWER, 1)) { + if (errno == ENODEV || errno == EPERM) + SKIP(return, "no unprivileged overlayfs"); + SKIP(return, "overlayfs unusable here: %s", strerror(errno)); + } + ASSERT_EQ(umount(MERGED), 0); + + EXPECT_NE(mount_overlay(MNT, 2), 0); +} + +/* An ordinary interpreter still registers with 'F'. */ +TEST_F(selfpin, ordinary_interpreter_still_works) +{ + EXPECT_EQ(register_at(_metadata, RULE(INTERP)), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/exec/binfmt_misc_transparent.c b/tools/testing/selftests/exec/binfmt_misc_transparent.c new file mode 100644 index 000000000000..2ebf73de8018 --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_misc_transparent.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test the static transparent flag 'T' of binfmt_misc. A magic-matched + * binary is dispatched to an interpreter with the argument vector left + * untouched, the binary passed through AT_EXECFD and mm->exe_file labeled + * with the binary. The asserting interpreter (binfmt_transparent_interp) + * verifies the constructed identity from inside the process and exits 0. + * + * Needs root for the registration; no bpf toolchain involved. + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> + +#include "binfmt_misc_common.h" +#include "kselftest_harness.h" + +#define MAGIC "#TRANSPARENT-SELFTEST#" +#define TARGET_PATH "/tmp/binfmt_transparent_target" +#define INTERP_PATH "/tmp/binfmt_transparent_interp" +#define ENTRY "test_transparent" +#define RULE(flags) ":" ENTRY ":M:0:" MAGIC "::" INTERP_PATH ":" flags + +/* The target only has to carry the magic; it is never actually loaded. */ +static int create_target(void) +{ + char buf[128] = MAGIC "\n"; + int fd; + + unlink(TARGET_PATH); + fd = open(TARGET_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755); + if (fd < 0) + return -1; + if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) { + close(fd); + return -1; + } + close(fd); + return 0; +} + +FIXTURE(transparent) { +}; + +FIXTURE_SETUP(transparent) +{ + char src[PATH_MAX]; + + if (getuid() != 0) + SKIP(return, "test must be run as root"); + if (!binfmt_misc_available()) + SKIP(return, "no binfmt_misc"); + + ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0); + ASSERT_EQ(copy_file(src, INTERP_PATH), 0); + ASSERT_EQ(create_target(), 0); + + /* Skip the whole suite on a kernel that does not know 'T'. */ + if (!binfmt_flag_supported('T')) { + ASSERT_EQ(errno, EINVAL); + SKIP(return, "kernel without the 'T' flag"); + } +} + +FIXTURE_TEARDOWN(transparent) +{ + unregister(ENTRY); + unlink(TARGET_PATH); + unlink(INTERP_PATH); +} + +/* Grammar sanity check: the same entry without 'T' has to register. */ +TEST_F(transparent, plain_entry_registers) +{ + ASSERT_EQ(write_reg(RULE("")), 0); +} + +/* 'T' preserves the whole argv, so combining it with 'P' is rejected. */ +TEST_F(transparent, rejects_preserve_argv0) +{ + ASSERT_NE(write_reg(RULE("TP")), 0); + EXPECT_EQ(errno, EINVAL); +} + +/* The interpreter asserts the identity the kernel built for it. */ +TEST_F(transparent, dispatch) +{ + ASSERT_EQ(write_reg(RULE("T")), 0); + + setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1); + setenv("BINFMT_TEST_ARGV0", PAYLOAD_ARGV0, 1); + EXPECT_EQ(run_payload(TARGET_PATH), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/exec/binfmt_transparent_interp.c b/tools/testing/selftests/exec/binfmt_transparent_interp.c new file mode 100644 index 000000000000..d4c4a538c9aa --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_transparent_interp.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Asserting interpreter for the transparent binfmt_misc mode. It runs in + * place of the dispatched binary and verifies the identity the kernel + * constructed: the aux vector contract, the exe link, argv, cmdline, comm + * and the write denial on the binary. BINFMT_TEST_BINARY names the binary; + * the harness execs it with the arguments "argone argtwo". Prints + * TRANSPARENT_OK and exits 0 when every check holds. + */ +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/auxv.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "binfmt_misc_common.h" +#include "kselftest.h" + +#ifndef AT_FLAGS_TRANSPARENT_INTERP +#define AT_FLAGS_TRANSPARENT_INTERP (1 << 1) +#endif + +static int fail; + +static void ok(int cond, const char *what) +{ + if (!cond) { + fprintf(stderr, "TRANSPARENT_FAIL: %s (errno %d)\n", what, errno); + fail = 1; + } +} + +int main(int argc, char **argv) +{ + const char *binary = getenv("BINFMT_TEST_BINARY"); + const char *argv0 = getenv("BINFMT_TEST_ARGV0"); + char expect[PATH_MAX + 32], buf[PATH_MAX]; + unsigned long execfd; + struct stat stb, stfd; + const char *want[3]; + const char *base; + size_t expect_len, i; + int fd, have_stb, have_stfd; + ssize_t n; + + if (!binary) { + fprintf(stderr, "TRANSPARENT_FAIL: BINFMT_TEST_BINARY unset\n"); + return 1; + } + /* Distinct from the binary path, so a classic argv splice is caught. */ + want[0] = argv0 ? argv0 : binary; + want[1] = PAYLOAD_ARG1; + want[2] = PAYLOAD_ARG2; + + /* The aux vector announces the transparent contract. */ + ok(getauxval(AT_FLAGS) & AT_FLAGS_TRANSPARENT_INTERP, + "AT_FLAGS lacks AT_FLAGS_TRANSPARENT_INTERP"); + + /* AT_EXECFD refers to the very file that was executed. */ + execfd = getauxval(AT_EXECFD); + ok(execfd > 2, "no AT_EXECFD"); + have_stb = !stat(binary, &stb); + ok(have_stb, "cannot stat the binary"); + have_stfd = !fstat((int)execfd, &stfd); + ok(have_stfd, "cannot fstat AT_EXECFD"); + ok(have_stb && have_stfd && stb.st_dev == stfd.st_dev && + stb.st_ino == stfd.st_ino, "AT_EXECFD is not the binary"); + + /* The exe link names the binary, not this interpreter. */ + ok(exe_is(binary), "/proc/self/exe is not the binary"); + + /* argv arrived unspliced. */ + ok(argc == (int)ARRAY_SIZE(want), "argv was rewritten"); + for (i = 0; i < ARRAY_SIZE(want) && i < (size_t)argc; i++) + ok(!strcmp(argv[i], want[i]), "argv was rewritten"); + + /* And so did the kernel's copy of it: the same strings, NUL separated. */ + for (i = 0, expect_len = 0; i < ARRAY_SIZE(want); i++) { + size_t len = strlen(want[i]) + 1; + + if (expect_len + len > sizeof(expect)) { + ok(0, "argv does not fit the expectation buffer"); + break; + } + memcpy(expect + expect_len, want[i], len); + expect_len += len; + } + fd = open("/proc/self/cmdline", O_RDONLY); + n = fd >= 0 ? read(fd, buf, sizeof(buf)) : -1; + if (fd >= 0) + close(fd); + ok(n == (ssize_t)expect_len && !memcmp(buf, expect, expect_len), + "/proc/self/cmdline was rewritten"); + + /* comm is the binary's basename. */ + base = strrchr(binary, '/'); + base = base ? base + 1 : binary; + ok(comm_is(base), "comm is not the binary's basename"); + + /* The binary is write-denied while it runs, like a direct exec. */ + ok(write_denied(binary), "binary is writable while running"); + ok(write_denied("/proc/self/exe"), "exe link is writable while running"); + + if (!fail) + printf("TRANSPARENT_OK\n"); + return fail; +} diff --git a/tools/testing/selftests/exec/bpf_interp.bpf.c b/tools/testing/selftests/exec/bpf_interp.bpf.c new file mode 100644 index 000000000000..8df2d2d01e25 --- /dev/null +++ b/tools/testing/selftests/exec/bpf_interp.bpf.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * binfmt_misc_ops handler for the selftest's fixed-interpreter case: match a + * 64-bit aarch64 ELF header from the prefetched buffer and route it to a fixed + * interpreter chosen by the program. This is the portable, self-contained + * equivalent of routing a foreign binary to an emulator: it matches + * programmatically and computes the interpreter, but points at a test binary + * the harness installs rather than a system emulator. + */ +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +char _license[] SEC("license") = "GPL"; + +#define EI_CLASS 4 +#define ELFCLASS64 2 +#define EM_AARCH64 183 + +extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path, + size_t path__sz) __ksym; + +/* + * A magic-style decision needs nothing beyond the prefetched bprm->buf, + * even though the match program could read the file. + */ +SEC("struct_ops.s/match") +bool BPF_PROG(bpf_interp_match, struct linux_binprm *bprm) +{ + __u16 machine; + + if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' || + bprm->buf[2] != 'L' || bprm->buf[3] != 'F' || + bprm->buf[EI_CLASS] != ELFCLASS64) + return false; + + /* e_machine is a 16-bit little-endian field at offset 18. */ + machine = (__u8)bprm->buf[18] | ((__u16)(__u8)bprm->buf[19] << 8); + return machine == EM_AARCH64; +} + +SEC("struct_ops.s/load") +int BPF_PROG(bpf_interp_load, struct linux_binprm *bprm) +{ + /* + * Keep the path on the (writable) stack: bpf_binprm_set_interp() takes + * a sized memory arg and the verifier rejects a read-only .rodata + * buffer for it. The harness installs the interpreter at this path. + */ + char interp[] = "/tmp/binfmt_bpf_interp"; + + /* @path__sz includes the terminating NUL; 0 commits the selection. */ + return bpf_binprm_set_interp(bprm, interp, sizeof(interp)); +} + +SEC(".struct_ops.link") +struct binfmt_misc_ops bpf_interp = { + .match = (void *)bpf_interp_match, + .load = (void *)bpf_interp_load, + .name = "bpf_interp", +}; diff --git a/tools/testing/selftests/exec/config b/tools/testing/selftests/exec/config index c308079867b3..ea359a929ae8 100644 --- a/tools/testing/selftests/exec/config +++ b/tools/testing/selftests/exec/config @@ -1,2 +1,12 @@ CONFIG_BLK_DEV=y CONFIG_BLK_DEV_LOOP=y +CONFIG_BINFMT_MISC=y +CONFIG_BINFMT_MISC_BPF=y +CONFIG_BPF_JIT=y +CONFIG_BPF_SYSCALL=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_OVERLAY_FS=y +CONFIG_TMPFS=y +CONFIG_USER_NS=y diff --git a/tools/testing/selftests/exec/interp_bind.bpf.c b/tools/testing/selftests/exec/interp_bind.bpf.c new file mode 100644 index 000000000000..1ce45cca215f --- /dev/null +++ b/tools/testing/selftests/exec/interp_bind.bpf.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * binfmt_misc_ops handler for the selftest's bound-interpreter case: one + * handler, one entry, an interpreter per guest architecture - each bound to + * a file when the entry was registered rather than to a path resolved at + * exec time. The load program names the one it wants; a name the entry did + * not bind fails the exec, which the harness checks too. + */ +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +char _license[] SEC("license") = "GPL"; + +#define EI_CLASS 4 +#define ELFCLASS64 2 +#define E_MACHINE_OFF 18 +#define EM_ARM 40 +#define EM_AARCH64 183 +#define EM_RISCV 243 + +extern int bpf_binprm_select_interp(struct linux_binprm *bprm, + const char *name, size_t name__sz) __ksym; + +/* The guest architecture of a 64-bit ELF, or zero if it is not one. */ +static __u16 elf_machine(struct linux_binprm *bprm) +{ + if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' || + bprm->buf[2] != 'L' || bprm->buf[3] != 'F' || + bprm->buf[EI_CLASS] != ELFCLASS64) + return 0; + + /* Little-endian 16-bit field, read byte-wise for the verifier. */ + return (__u8)bprm->buf[E_MACHINE_OFF] | + ((__u16)(__u8)bprm->buf[E_MACHINE_OFF + 1] << 8); +} + +SEC("struct_ops.s/match") +bool BPF_PROG(interp_bind_match, struct linux_binprm *bprm) +{ + __u16 machine = elf_machine(bprm); + + return machine == EM_AARCH64 || machine == EM_RISCV || + machine == EM_ARM; +} + +SEC("struct_ops.s/load") +int BPF_PROG(interp_bind_load, struct linux_binprm *bprm) +{ + /* + * Names, not paths: each one selects a file the entry pre-opened, so + * nothing is resolved here or later, in any namespace. The buffers + * are on the stack because the verifier rejects .rodata for a sized + * memory argument. + */ + char first[] = "first"; + char second[] = "second"; + char unbound[] = "unbound"; + + switch (elf_machine(bprm)) { + case EM_AARCH64: + return bpf_binprm_select_interp(bprm, first, sizeof(first)); + case EM_RISCV: + return bpf_binprm_select_interp(bprm, second, sizeof(second)); + } + + /* The entry bound nothing under this name: -ENOENT fails the exec. */ + return bpf_binprm_select_interp(bprm, unbound, sizeof(unbound)); +} + +SEC(".struct_ops.link") +struct binfmt_misc_ops interp_bind = { + .match = (void *)interp_bind_match, + .load = (void *)interp_bind_load, + .name = "interp_bind", +}; diff --git a/tools/testing/selftests/exec/loader.bpf.c b/tools/testing/selftests/exec/loader.bpf.c new file mode 100644 index 000000000000..108e51dd4961 --- /dev/null +++ b/tools/testing/selftests/exec/loader.bpf.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * binfmt_misc_ops handler for the loader-substitution case: match the + * marker the harness poked into the payload's e_ident padding and ask for + * the selected interpreter to be substituted for the binary's PT_INTERP, + * so the binary itself runs as a fully native exec. + */ +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +char _license[] SEC("license") = "GPL"; + +#define EI_CLASS 4 +#define EI_PAD 9 +#define ELFCLASS64 2 + +extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path, + size_t path__sz) __ksym; +extern int bpf_binprm_set_flags(struct linux_binprm *bprm, + enum bpf_binprm_flags flags) __ksym; + +SEC("struct_ops.s/match") +bool BPF_PROG(loader_match, struct linux_binprm *bprm) +{ + if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' || + bprm->buf[2] != 'L' || bprm->buf[3] != 'F' || + bprm->buf[EI_CLASS] != ELFCLASS64) + return false; + + /* The harness marks the payload with "LDRTST" at EI_PAD. */ + return bprm->buf[EI_PAD + 0] == 'L' && bprm->buf[EI_PAD + 1] == 'D' && + bprm->buf[EI_PAD + 2] == 'R' && bprm->buf[EI_PAD + 3] == 'T' && + bprm->buf[EI_PAD + 4] == 'S' && bprm->buf[EI_PAD + 5] == 'T'; +} + +SEC("struct_ops.s/load") +int BPF_PROG(loader_load, struct linux_binprm *bprm) +{ + char interp[] = "/tmp/binfmt_loader_interp"; + int err; + + err = bpf_binprm_set_flags(bprm, BPF_BINPRM_LOADER); + if (err) + return err; + + /* @path__sz includes the terminating NUL; 0 commits the selection. */ + return bpf_binprm_set_interp(bprm, interp, sizeof(interp)); +} + +SEC(".struct_ops.link") +struct binfmt_misc_ops loader = { + .match = (void *)loader_match, + .load = (void *)loader_load, + .name = "loader", +}; diff --git a/tools/testing/selftests/exec/nix_origin.bpf.c b/tools/testing/selftests/exec/nix_origin.bpf.c new file mode 100644 index 000000000000..378e22a4c43b --- /dev/null +++ b/tools/testing/selftests/exec/nix_origin.bpf.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * nix_origin.bpf.c - $ORIGIN-relative PT_INTERP resolution + * + * A binfmt_misc_ops handler that makes relocatable (Nix-style) ELF + * binaries work: if PT_INTERP starts with "$ORIGIN/", the loader is + * resolved relative to the directory of the binary being executed and + * selected via bpf_binprm_set_interp(). The match program reads the + * program headers itself, so anything else never commits to this + * handler and passes through untouched. + * + * Activate with: + * bpftool struct_ops register nix_origin.bpf.o /sys/fs/bpf + * echo ':nix-origin:B::::nix_origin:' > /proc/sys/fs/binfmt_misc/register + */ +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +char _license[] SEC("license") = "GPL"; + +#define PATH_MAX 4096 +#define EI_CLASS 4 +#define ELFCLASSXX 2 /* ELFCLASS64; flip to 1 for 32-bit */ +#define PT_INTERP 3 +#define MAX_PHDRS 64 + +#define ORIGIN "$ORIGIN" +#define ORIGIN_LEN (sizeof(ORIGIN) - 1) + +#define ENOENT 2 +#define ENOEXEC 8 +#define ENAMETOOLONG 36 + +extern int bpf_dynptr_from_file(struct file *file, __u32 flags, + struct bpf_dynptr *ptr__uninit) __ksym; +extern int bpf_dynptr_file_discard(struct bpf_dynptr *dynptr) __ksym; +extern int bpf_path_d_path(const struct path *path, char *buf, + size_t buf__sz) __ksym; +extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path, + size_t path__sz) __ksym; + +struct scratch { + char interp[PATH_MAX]; /* PT_INTERP as embedded in the binary */ + char path[PATH_MAX]; /* d_path of the binary, becomes the result */ +}; + +/* Keyed by pid: execs run concurrently and the programs can sleep. */ +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 512); + __type(key, __u64); + __type(value, struct scratch); +} scratch_map SEC(".maps"); + +static const struct scratch zero_scratch; + +/* An ELF64 binary per the prefetched header? */ +static bool is_elf64(struct linux_binprm *bprm) +{ + return bprm->buf[0] == 0x7f && bprm->buf[1] == 'E' && + bprm->buf[2] == 'L' && bprm->buf[3] == 'F' && + bprm->buf[EI_CLASS] == ELFCLASSXX; +} + +/* Locate PT_INTERP; false if the file has none or looks malformed. */ +static bool find_pt_interp(struct bpf_dynptr *dp, struct elf64_phdr *phdr) +{ + struct elf64_hdr ehdr; + bool found = false; + int i; + + if (bpf_dynptr_read(&ehdr, sizeof(ehdr), dp, 0, 0)) + return false; + if (ehdr.e_phentsize != sizeof(struct elf64_phdr)) + return false; + + bpf_for(i, 0, ehdr.e_phnum) { + if (i >= MAX_PHDRS) + break; + if (bpf_dynptr_read(phdr, sizeof(*phdr), dp, + ehdr.e_phoff + i * sizeof(*phdr), 0)) + return false; + if (phdr->p_type == PT_INTERP) { + found = true; + break; + } + } + return found; +} + +/* + * An ELF64 binary whose PT_INTERP starts with "$ORIGIN/" is ours. The + * match can sleep and read the file, so the decision is made here and + * regular binaries never commit to this handler: later binfmt_misc + * entries and binfmt_elf see them as if we did not exist. + */ +SEC("struct_ops.s/match") +bool BPF_PROG(nix_origin_match, struct linux_binprm *bprm) +{ + char prefix[ORIGIN_LEN + 1] = {}; + struct elf64_phdr phdr; + struct bpf_dynptr dp; + bool ours = false; + + if (!is_elf64(bprm)) + return false; + + /* The dynptr must be discarded on every path once requested. */ + if (bpf_dynptr_from_file(bprm->file, 0, &dp)) + goto out; + if (find_pt_interp(&dp, &phdr) && + phdr.p_filesz > ORIGIN_LEN + 1 && + !bpf_dynptr_read(prefix, sizeof(prefix), &dp, phdr.p_offset, 0)) + ours = !bpf_strncmp(prefix, sizeof(prefix), ORIGIN "/"); +out: + bpf_dynptr_file_discard(&dp); + return ours; +} + +/* + * The match is committed and already vetted the "$ORIGIN/" prefix, so + * everything here reads the file again from scratch: -ENOEXEC only + * covers a binary that changed under us and stopped being ours. + */ +SEC("struct_ops.s/load") +int BPF_PROG(nix_origin_load, struct linux_binprm *bprm) +{ + __u32 isz, sfx, rsz, slash; + struct elf64_phdr phdr; + struct bpf_dynptr dp; + struct scratch *sc; + __u64 id; + int ret = -ENOEXEC, len, i; + + if (bpf_dynptr_from_file(bprm->file, 0, &dp)) + goto out; + + if (!find_pt_interp(&dp, &phdr)) + goto out; + + isz = phdr.p_filesz; + if (isz <= ORIGIN_LEN + 1 || isz >= sizeof(sc->interp)) + goto out; + /* + * The range check above compiles to a test on a zero-extended copy of + * the u64 p_filesz, so the verifier does not carry the bound to the + * dynptr_read() length below ("unbounded memory access"). Mask isz to + * the buffer size (a power of two) and force the masked value to be + * materialized with a barrier so the read uses the bounded register. + */ + isz &= sizeof(sc->interp) - 1; + barrier_var(isz); + + id = bpf_get_current_pid_tgid(); + if (bpf_map_update_elem(&scratch_map, &id, &zero_scratch, BPF_ANY)) + goto out; + sc = bpf_map_lookup_elem(&scratch_map, &id); + if (!sc) + goto out_del; + + if (bpf_dynptr_read(sc->interp, isz, &dp, phdr.p_offset, 0)) + goto out_del; + if (sc->interp[isz - 1] != '\0') + goto out_del; + + /* Not "$ORIGIN/..." anymore? Then it is not ours anymore either. */ + if (sc->interp[0] != '$' || sc->interp[1] != 'O' || + sc->interp[2] != 'R' || sc->interp[3] != 'I' || + sc->interp[4] != 'G' || sc->interp[5] != 'I' || + sc->interp[6] != 'N' || sc->interp[7] != '/') + goto out_del; + + /* + * From here on resolution failures fail the exec instead of falling + * back to binfmt_elf, which would resolve the literal "$ORIGIN/..." + * relative to the caller's cwd. + */ + ret = -ENOENT; + len = bpf_path_d_path(&bprm->file->f_path, sc->path, sizeof(sc->path)); + if (len <= 0 || len > sizeof(sc->path)) + goto out_del; + /* Unreachable or unlinked ("... (deleted)") binaries can't resolve. */ + if (sc->path[0] != '/') + goto out_del; + + /* $ORIGIN = dirname of the binary. */ + slash = 0; + bpf_for(i, 1, len - 1) { + if (i >= sizeof(sc->path)) + break; + if (sc->path[i] == '/') + slash = i; + } + + /* Splice the suffix (leading '/' and NUL included) onto the dir. */ + sfx = isz - ORIGIN_LEN; + rsz = slash + sfx; + if (rsz > sizeof(sc->path)) { + ret = -ENAMETOOLONG; + goto out_del; + } + bpf_for(i, 0, sfx) { + __u32 s = ORIGIN_LEN + i, d = slash + i; + + if (s >= sizeof(sc->interp) || d >= sizeof(sc->path)) + break; + sc->path[d] = sc->interp[s]; + } + + ret = bpf_binprm_set_interp(bprm, sc->path, rsz); +out_del: + bpf_map_delete_elem(&scratch_map, &id); +out: + bpf_dynptr_file_discard(&dp); + return ret; +} + +SEC(".struct_ops.link") +struct binfmt_misc_ops nix_origin = { + .match = (void *)nix_origin_match, + .load = (void *)nix_origin_load, + .name = "nix_origin", +}; diff --git a/tools/testing/selftests/exec/transparent.bpf.c b/tools/testing/selftests/exec/transparent.bpf.c new file mode 100644 index 000000000000..7632019ebe69 --- /dev/null +++ b/tools/testing/selftests/exec/transparent.bpf.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * binfmt_misc_ops handler for the transparent-mode case: match a synthetic + * riscv ELF header and run the asserting interpreter transparently - the + * argument vector untouched, the binary in AT_EXECFD and mm->exe_file + * labeled with the binary. + */ +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +char _license[] SEC("license") = "GPL"; + +#define EI_CLASS 4 +#define ELFCLASS64 2 +#define EM_RISCV 243 + +extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path, + size_t path__sz) __ksym; +extern int bpf_binprm_set_flags(struct linux_binprm *bprm, + enum bpf_binprm_flags flags) __ksym; + +SEC("struct_ops.s/match") +bool BPF_PROG(transparent_match, struct linux_binprm *bprm) +{ + __u16 machine; + + if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' || + bprm->buf[2] != 'L' || bprm->buf[3] != 'F' || + bprm->buf[EI_CLASS] != ELFCLASS64) + return false; + + /* e_machine is a 16-bit little-endian field at offset 18. */ + machine = (__u8)bprm->buf[18] | ((__u16)(__u8)bprm->buf[19] << 8); + return machine == EM_RISCV; +} + +SEC("struct_ops.s/load") +int BPF_PROG(transparent_load, struct linux_binprm *bprm) +{ + char interp[] = "/tmp/binfmt_transparent_interp"; + int err; + + err = bpf_binprm_set_flags(bprm, BPF_BINPRM_TRANSPARENT); + if (err) + return err; + + /* @path__sz includes the terminating NUL; 0 commits the selection. */ + return bpf_binprm_set_interp(bprm, interp, sizeof(interp)); +} + +SEC(".struct_ops.link") +struct binfmt_misc_ops transparent = { + .match = (void *)transparent_match, + .load = (void *)transparent_load, + .name = "transparent", +}; diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index 64ac0dfa46b7..9eb185fb2f9d 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -5,3 +5,5 @@ fclog file_stressor anon_inode_test kernfs_test +idmapped_tmpfile +ustat_test diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index 85427d7f19b9..03be337c1f35 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,11 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS += $(KHDR_INCLUDES) -TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog +TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test +TEST_GEN_PROGS += idmapped_tmpfile TEST_GEN_PROGS_EXTENDED := dnotify_test include ../lib.mk + +$(OUTPUT)/idmapped_tmpfile: LDLIBS += -lcap +$(OUTPUT)/idmapped_tmpfile: utils.c diff --git a/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c b/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c index f6f1a7ff01b0..81a994943e12 100644 --- a/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c +++ b/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c @@ -3538,4 +3538,27 @@ TEST(epoll65) close(ctx.efd[1]); } +TEST(epoll66) +{ + struct epoll_event event; + int pfd[2], efd; + + ASSERT_EQ(pipe(pfd), 0); + + efd = epoll_create1(0); + ASSERT_GE(efd, 0); + + event.events = EPOLLIN | EPOLLET; + ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event), 0); + + for (int i = 0; i < 2; ++i) { + ASSERT_EQ(write(pfd[1], "", 1), 1); + EXPECT_EQ(epoll_wait(efd, &event, 1, 0), 1); + } + + close(pfd[0]); + close(pfd[1]); + close(efd); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/failfs/.gitignore b/tools/testing/selftests/filesystems/failfs/.gitignore new file mode 100644 index 000000000000..cd3b5d884d7e --- /dev/null +++ b/tools/testing/selftests/filesystems/failfs/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +failfs_test diff --git a/tools/testing/selftests/filesystems/failfs/Makefile b/tools/testing/selftests/filesystems/failfs/Makefile new file mode 100644 index 000000000000..3c5d98b4fe72 --- /dev/null +++ b/tools/testing/selftests/filesystems/failfs/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) +TEST_GEN_PROGS := failfs_test + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/failfs/failfs_test.c b/tools/testing/selftests/filesystems/failfs/failfs_test.c new file mode 100644 index 000000000000..29a3c294127e --- /dev/null +++ b/tools/testing/selftests/filesystems/failfs/failfs_test.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <link.h> +#include <sched.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/vfs.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "../../kselftest_harness.h" + +#ifndef __NR_fchroot +#define __NR_fchroot 472 +#endif + +#ifndef FD_PIDFS_ROOT +#define FD_PIDFS_ROOT -10002 +#endif + +#ifndef FD_NSFS_ROOT +#define FD_NSFS_ROOT -10003 +#endif + +#ifndef FD_FAILFS_ROOT +#define FD_FAILFS_ROOT -10004 +#endif + +#define NOBODY_UID 65534 + +/* Child sentinel exit code: the exec was blocked as expected. */ +#define FAILFS_EXEC_BLOCKED 99 + +/* Stack for the CLONE_FS helper in fchroot_sentinel_shared_fs_struct. */ +#define FAILFS_CLONE_STACK (64 * 1024) + +static int sys_fchroot(int fd, unsigned int flags) +{ + return syscall(__NR_fchroot, fd, flags); +} + +/* + * Raw syscall: glibc's getcwd() rejects the kernel's "(unreachable)" + * result and falls back to a generic implementation. + */ +static long sys_getcwd(char *buf, size_t size) +{ + return syscall(__NR_getcwd, buf, size); +} + +static int drop_to_nobody(void) +{ + return setresuid(NOBODY_UID, NOBODY_UID, NOBODY_UID); +} + +/* Parked CLONE_FS child; dies with its parent so it never leaks. */ +static int failfs_park(void *arg) +{ + pid_t parent = (pid_t)(long)arg; + + prctl(PR_SET_PDEATHSIG, SIGKILL); + /* The parent may have died before the death signal was armed. */ + if (getppid() != parent) + _exit(0); + pause(); + return 0; +} + +/* Is fd a dynamically linked ELF with an absolute PT_INTERP interpreter? */ +static int elf_has_absolute_interp(int fd) +{ + ElfW(Ehdr) ehdr; + ElfW(Phdr) phdr; + char interp; + int i; + + if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr)) + return 0; + if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) + return 0; + + for (i = 0; i < ehdr.e_phnum; i++) { + if (pread(fd, &phdr, sizeof(phdr), + ehdr.e_phoff + i * sizeof(phdr)) != sizeof(phdr)) + return 0; + if (phdr.p_type != PT_INTERP) + continue; + if (pread(fd, &interp, 1, phdr.p_offset) != 1) + return 0; + return interp == '/'; + } + + return 0; +} + +TEST(fchdir_sentinel) +{ + char buf[PATH_MAX]; + int fd; + + ASSERT_EQ(fchdir(FD_FAILFS_ROOT), 0); + + /* The working directory is unreachable from the process root. */ + ASSERT_GT(sys_getcwd(buf, sizeof(buf)), 0); + ASSERT_EQ(strncmp(buf, "(unreachable)", 13), 0); + + /* Every AT_FDCWD-relative lookup fails. */ + ASSERT_EQ(openat(AT_FDCWD, "foo", O_RDONLY), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + ASSERT_EQ(openat(AT_FDCWD, ".", O_RDONLY), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + ASSERT_EQ(openat(AT_FDCWD, "..", O_RDONLY), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + ASSERT_EQ(openat(AT_FDCWD, "foo", O_WRONLY | O_CREAT, 0600), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* The cwd cannot be pinned by following /proc/self/cwd into it. */ + ASSERT_EQ(open("/proc/self/cwd", O_PATH), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* The root is untouched so absolute lookups keep working... */ + fd = open("/", O_RDONLY | O_DIRECTORY); + ASSERT_GE(fd, 0); + ASSERT_EQ(close(fd), 0); + + /* ... and the working directory can be recovered. */ + ASSERT_EQ(chdir("/"), 0); + ASSERT_GT(sys_getcwd(buf, sizeof(buf)), 0); + ASSERT_EQ(strcmp(buf, "/"), 0); +} + +TEST(fchdir_rejects_other_sentinels) +{ + ASSERT_EQ(fchdir(FD_PIDFS_ROOT), -1); + ASSERT_EQ(errno, EBADF); + ASSERT_EQ(fchdir(FD_NSFS_ROOT), -1); + ASSERT_EQ(errno, EBADF); + ASSERT_EQ(fchdir(-10009), -1); + ASSERT_EQ(errno, EBADF); +} + +TEST(fchroot_flags) +{ + int fd; + + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 1), -1); + ASSERT_EQ(errno, EINVAL); + + fd = open("/", O_PATH | O_DIRECTORY); + ASSERT_GE(fd, 0); + ASSERT_EQ(sys_fchroot(fd, 1), -1); + ASSERT_EQ(errno, EINVAL); + ASSERT_EQ(close(fd), 0); +} + +TEST(fchroot_bad_fd) +{ + ASSERT_EQ(sys_fchroot(-1, 0), -1); + ASSERT_EQ(errno, EBADF); + + /* Only FD_FAILFS_ROOT is a valid sentinel. */ + ASSERT_EQ(sys_fchroot(FD_PIDFS_ROOT, 0), -1); + ASSERT_EQ(errno, EBADF); + ASSERT_EQ(sys_fchroot(FD_NSFS_ROOT, 0), -1); + ASSERT_EQ(errno, EBADF); +} + +TEST(fchroot_notdir) +{ + int fd; + + fd = open("/proc/self/status", O_RDONLY); + ASSERT_GE(fd, 0); + ASSERT_EQ(sys_fchroot(fd, 0), -1); + ASSERT_EQ(errno, ENOTDIR); + ASSERT_EQ(close(fd), 0); +} + +TEST(fchroot_realfd_requires_cap) +{ + int fd; + + if (geteuid() == 0) + ASSERT_EQ(drop_to_nobody(), 0); + + fd = open("/", O_PATH | O_DIRECTORY); + ASSERT_GE(fd, 0); + ASSERT_EQ(sys_fchroot(fd, 0), -1); + ASSERT_EQ(errno, EPERM); + ASSERT_EQ(close(fd), 0); +} + +TEST(fchroot_realfd) +{ + char template[] = "/tmp/failfs_test.XXXXXX"; + char path[PATH_MAX]; + struct stat st; + int tmpfd, dfd, fd; + + if (geteuid() != 0) + SKIP(return, "fchroot() with a regular fd requires CAP_SYS_CHROOT"); + + tmpfd = open("/tmp", O_PATH | O_DIRECTORY); + ASSERT_GE(tmpfd, 0); + + ASSERT_NE(mkdtemp(template), NULL); + snprintf(path, sizeof(path), "%s/canary", template); + fd = open(path, O_WRONLY | O_CREAT, 0600); + ASSERT_GE(fd, 0); + ASSERT_EQ(close(fd), 0); + + dfd = open(template, O_PATH | O_DIRECTORY); + ASSERT_GE(dfd, 0); + ASSERT_EQ(sys_fchroot(dfd, 0), 0); + ASSERT_EQ(close(dfd), 0); + + ASSERT_EQ(stat("/canary", &st), 0); + + /* Best-effort cleanup: dirfd-anchored I/O works with the new root. */ + snprintf(path, sizeof(path), "%s/canary", template + strlen("/tmp/")); + unlinkat(tmpfd, path, 0); + unlinkat(tmpfd, template + strlen("/tmp/"), AT_REMOVEDIR); +} + +TEST(fchroot_sentinel) +{ + char template[] = "/tmp/failfs_test.XXXXXX"; + struct stat realroot, st; + struct statfs sfs; + char buf[PATH_MAX]; + int procfd, tmpfd, dfd, fd; + struct { + struct file_handle handle; + unsigned char f_handle[MAX_HANDLE_SZ]; + } fh; + int mntid; + ssize_t ret; + + if (geteuid() != 0) + SKIP(return, "privileged fchroot(FD_FAILFS_ROOT) requires CAP_SYS_CHROOT"); + + ASSERT_EQ(stat("/", &realroot), 0); + procfd = open("/proc", O_PATH | O_DIRECTORY); + ASSERT_GE(procfd, 0); + tmpfd = open("/tmp", O_PATH | O_DIRECTORY); + ASSERT_GE(tmpfd, 0); + ASSERT_NE(mkdtemp(template), NULL); + dfd = open(template, O_RDONLY | O_DIRECTORY); + ASSERT_GE(dfd, 0); + + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), 0); + + /* Absolute lookups fail. */ + ASSERT_EQ(open("/etc/passwd", O_RDONLY), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + ASSERT_EQ(mkdir("/foo", 0700), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* + * The root cannot be referenced at all - not even an O_PATH open, + * which skips ->permission(), because it lands on the root as a + * jumped walk terminal that ->d_weak_revalidate() refuses. + */ + ASSERT_EQ(open("/", O_RDONLY | O_DIRECTORY), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + ASSERT_EQ(open("/", O_PATH), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + ASSERT_EQ(statfs("/", &sfs), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* + * It cannot be pinned by following /proc/self/root into it either + * (only the root is in failfs here, so self/cwd is still real). + */ + ASSERT_EQ(openat(procfd, "self/root", O_PATH), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* Nor encoded into a file handle. */ + fh.handle.handle_bytes = MAX_HANDLE_SZ; + ASSERT_EQ(name_to_handle_at(AT_FDCWD, "/", &fh.handle, &mntid, 0), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* The working directory is now unreachable from the root. */ + ASSERT_GT(sys_getcwd(buf, sizeof(buf)), 0); + ASSERT_EQ(strncmp(buf, "(unreachable)", 13), 0); + + /* Lookups anchored at real directories keep working. */ + fd = openat(AT_FDCWD, ".", O_RDONLY | O_DIRECTORY); + ASSERT_GE(fd, 0); + ASSERT_EQ(close(fd), 0); + fd = openat(dfd, "canary", O_WRONLY | O_CREAT, 0600); + ASSERT_GE(fd, 0); + ASSERT_EQ(write(fd, "x", 1), 1); + ASSERT_EQ(close(fd), 0); + fd = openat(dfd, "canary", O_RDONLY); + ASSERT_GE(fd, 0); + ASSERT_EQ(close(fd), 0); + + /* ".." walks clamp at the top of the mount tree, not at failfs. */ + fd = openat(AT_FDCWD, "../../../../../../../../../..", O_PATH); + ASSERT_GE(fd, 0); + ASSERT_EQ(fstat(fd, &st), 0); + ASSERT_EQ(st.st_dev, realroot.st_dev); + ASSERT_EQ(st.st_ino, realroot.st_ino); + ASSERT_EQ(close(fd), 0); + + /* readlink of the magic link still works: it does not follow. */ + ret = readlinkat(procfd, "self/root", buf, sizeof(buf) - 1); + ASSERT_GT(ret, 0); + buf[ret] = '\0'; + TH_LOG("/proc/self/root points to '%s'", buf); + /* d_path() names the failfs root synthetically, never as a real path. */ + ASSERT_EQ(strcmp(buf, "failfs:/"), 0); + + /* But following it into failfs is refused. */ + ASSERT_EQ(fstatat(procfd, "self/root", &st, 0), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* Best-effort cleanup via the pre-opened dirfds. */ + unlinkat(dfd, "canary", 0); + unlinkat(tmpfd, template + strlen("/tmp/"), AT_REMOVEDIR); +} + +TEST(fchroot_sentinel_absolute_symlink) +{ + char template[] = "/tmp/failfs_test.XXXXXX"; + int tmpfd, dfd, fd; + + if (geteuid() != 0) + SKIP(return, "privileged fchroot(FD_FAILFS_ROOT) requires CAP_SYS_CHROOT"); + + tmpfd = open("/tmp", O_PATH | O_DIRECTORY); + ASSERT_GE(tmpfd, 0); + ASSERT_NE(mkdtemp(template), NULL); + dfd = open(template, O_RDONLY | O_DIRECTORY); + ASSERT_GE(dfd, 0); + + fd = openat(dfd, "target", O_WRONLY | O_CREAT, 0600); + ASSERT_GE(fd, 0); + ASSERT_EQ(close(fd), 0); + ASSERT_EQ(symlinkat("target", dfd, "rel"), 0); + ASSERT_EQ(symlinkat("/etc", dfd, "abs"), 0); + + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), 0); + + /* Relative symlinks keep resolving within the dirfd-anchored walk... */ + fd = openat(dfd, "rel", O_RDONLY); + ASSERT_GE(fd, 0); + ASSERT_EQ(close(fd), 0); + + /* ... absolute symlinks restart the walk at the failfs root. */ + ASSERT_EQ(openat(dfd, "abs", O_RDONLY), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* Best-effort cleanup via the pre-opened dirfds. */ + unlinkat(dfd, "abs", 0); + unlinkat(dfd, "rel", 0); + unlinkat(dfd, "target", 0); + unlinkat(tmpfd, template + strlen("/tmp/"), AT_REMOVEDIR); +} + +TEST(fchroot_sentinel_unprivileged) +{ + char buf[PATH_MAX]; + + if (geteuid() == 0) + ASSERT_EQ(drop_to_nobody(), 0); + + /* Without no_new_privs entering failfs is not allowed... */ + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), -1); + ASSERT_EQ(errno, EPERM); + + /* ... with no_new_privs set it is allowed. */ + ASSERT_EQ(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0); + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), 0); + + ASSERT_EQ(open("/etc/passwd", O_RDONLY), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* The task counts as chrooted: no user namespaces anymore. */ + ASSERT_EQ(unshare(CLONE_NEWUSER), -1); + ASSERT_EQ(errno, EPERM); + + /* With both root and cwd in failfs getcwd() reports "/". */ + ASSERT_EQ(fchdir(FD_FAILFS_ROOT), 0); + ASSERT_GT(sys_getcwd(buf, sizeof(buf)), 0); + ASSERT_EQ(strcmp(buf, "/"), 0); +} + +TEST(fchroot_sentinel_rejected_when_chrooted) +{ + char template[] = "/tmp/failfs_test.XXXXXX"; + int tmpfd; + + if (geteuid() != 0) + SKIP(return, "chroot() requires CAP_SYS_CHROOT"); + + tmpfd = open("/tmp", O_PATH | O_DIRECTORY); + ASSERT_GE(tmpfd, 0); + ASSERT_NE(mkdtemp(template), NULL); + ASSERT_EQ(chroot(template), 0); + ASSERT_EQ(chdir("/"), 0); + + /* Remove the jail while still privileged; sticky /tmp blocks nobody. */ + unlinkat(tmpfd, template + strlen("/tmp/"), AT_REMOVEDIR); + + ASSERT_EQ(drop_to_nobody(), 0); + ASSERT_EQ(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0); + + /* An unprivileged chrooted task must not lift its ".." barrier. */ + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), -1); + ASSERT_EQ(errno, EPERM); +} + +TEST(fchroot_sentinel_shared_fs_struct) +{ + char stack[FAILFS_CLONE_STACK]; + pid_t pid; + + if (geteuid() == 0) + ASSERT_EQ(drop_to_nobody(), 0); + + /* A CLONE_FS sibling shares the fs_struct: bump fs->users to 2. */ + pid = clone(failfs_park, stack + sizeof(stack), CLONE_FS | SIGCHLD, + (void *)(long)getpid()); + ASSERT_GE(pid, 0); + + ASSERT_EQ(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0); + + /* + * A sibling without no_new_privs could exec a setuid binary with + * the failfs root, so a shared fs_struct is refused even with + * no_new_privs set. + */ + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), -1); + ASSERT_EQ(errno, EINVAL); + + ASSERT_EQ(kill(pid, SIGKILL), 0); + ASSERT_EQ(waitpid(pid, NULL, 0), pid); +} + +TEST(fchroot_sentinel_no_overmount) +{ + if (geteuid() != 0) + SKIP(return, "mounting requires privileges"); + + /* + * Contain the blast radius: if failfs ever regressed and "/" + * resolved to the real root, the tmpfs mount below must not touch + * the host. A private mount namespace keeps it local to this child. + */ + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), 0); + + /* + * Nothing can be mounted on top of the failfs root. It cannot even + * be named as a mount target: resolving "/" is refused before the + * mount machinery (which, failfs being in no mount namespace, would + * reject it anyway) is ever reached. open_tree(OPEN_TREE_CLONE) is + * likewise moot since no fd to the root can be obtained. + */ + ASSERT_EQ(mount("none", "/", "tmpfs", 0, NULL), -1); + ASSERT_EQ(errno, EOPNOTSUPP); +} + +TEST(fchroot_sentinel_setns_escape) +{ + struct stat realroot, st; + int nsfd; + + if (geteuid() != 0) + SKIP(return, "setns() to a mount namespace requires privileges"); + + ASSERT_EQ(stat("/", &realroot), 0); + nsfd = open("/proc/self/ns/mnt", O_RDONLY); + ASSERT_GE(nsfd, 0); + + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), 0); + ASSERT_EQ(open("/etc", O_PATH), -1); + ASSERT_EQ(errno, EOPNOTSUPP); + + /* A mount namespace fd is the key out: it resets root and cwd. */ + ASSERT_EQ(setns(nsfd, CLONE_NEWNS), 0); + ASSERT_EQ(close(nsfd), 0); + + ASSERT_EQ(stat("/", &st), 0); + ASSERT_EQ(st.st_dev, realroot.st_dev); + ASSERT_EQ(st.st_ino, realroot.st_ino); +} + +TEST(fchroot_sentinel_exec) +{ + pid_t pid; + int status; + + if (geteuid() != 0) + SKIP(return, "privileged fchroot(FD_FAILFS_ROOT) requires CAP_SYS_CHROOT"); + + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), 0); + + /* + * Exec in a child: a wrongly successful exec would replace the test + * image and its exit code would not match the sentinel below. + */ + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + execl("/bin/true", "true", NULL); + _exit(errno == EOPNOTSUPP ? FAILFS_EXEC_BLOCKED : 1); + } + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), FAILFS_EXEC_BLOCKED); +} + +TEST(fchroot_sentinel_exec_interpreter) +{ + static const char * const argv[] = { "failfs_test", NULL }; + static const char * const envp[] = { NULL }; + pid_t pid; + int status, exefd; + + if (geteuid() != 0) + SKIP(return, "privileged fchroot(FD_FAILFS_ROOT) requires CAP_SYS_CHROOT"); + + /* Exec ourselves: the one binary guaranteed to be around. */ + exefd = open("/proc/self/exe", O_RDONLY); + ASSERT_GE(exefd, 0); + if (!elf_has_absolute_interp(exefd)) + SKIP(return, "test binary has no absolute PT_INTERP interpreter"); + + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), 0); + + /* + * The binary itself needs no path lookup - it is executed by fd - + * but loading it fails on opening the absolute PT_INTERP + * interpreter. Run it in a child so a wrongly successful exec does + * not replace the test image and masquerade as a pass. + */ + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + syscall(__NR_execveat, exefd, "", argv, envp, AT_EMPTY_PATH); + _exit(errno == EOPNOTSUPP ? FAILFS_EXEC_BLOCKED : 1); + } + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), FAILFS_EXEC_BLOCKED); +} + +TEST(fchroot_sentinel_inherited) +{ + pid_t pid; + int status; + + if (geteuid() != 0) + SKIP(return, "privileged fchroot(FD_FAILFS_ROOT) requires CAP_SYS_CHROOT"); + + ASSERT_EQ(sys_fchroot(FD_FAILFS_ROOT, 0), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + if (open("/etc", O_PATH) != -1 || errno != EOPNOTSUPP) + _exit(1); + _exit(0); + } + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/fclog.c b/tools/testing/selftests/filesystems/fclog.c index 551c4a0f395a..593a5136e991 100644 --- a/tools/testing/selftests/filesystems/fclog.c +++ b/tools/testing/selftests/filesystems/fclog.c @@ -6,10 +6,8 @@ #include <assert.h> #include <errno.h> +#include <fcntl.h> #include <sched.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> #include <unistd.h> #include <sys/mount.h> diff --git a/tools/testing/selftests/filesystems/fscontext_ns/Makefile b/tools/testing/selftests/filesystems/fscontext_ns/Makefile new file mode 100644 index 000000000000..7e3506294757 --- /dev/null +++ b/tools/testing/selftests/filesystems/fscontext_ns/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := fscontext_ns_test + +CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) +LDLIBS := -lcap + +include ../../lib.mk + +$(OUTPUT)/fscontext_ns_test: fscontext_ns_test.c ../utils.c + $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) diff --git a/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c b/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c new file mode 100644 index 000000000000..0f30a3e6e197 --- /dev/null +++ b/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Christian Brauner <brauner@kernel.org> + * + * Test that completing a filesystem context from another user namespace + * doesn't warn. + * + * fsopen() records the caller's user namespace in fc->user_ns and hands + * back an ordinary file descriptor. The task that issues + * FSCONFIG_CMD_CREATE need not be the one that created the context: the fd + * is inherited across fork() and exec() and it can be passed over a unix + * socket. vfs_cmd_create() authorizes the create with mount_capable(), + * which for FS_USERNS_MOUNT checks ns_capable(fc->user_ns, CAP_SYS_ADMIN), + * and that succeeds for a task holding CAP_SYS_ADMIN in an ancestor of + * fc->user_ns. + * + * binfmt_misc and overlayfs used to WARN_ON() that mismatch, which let an + * unprivileged user taint the kernel, flood the log and panic a kernel + * booted with panic_on_warn. The mount must still be refused, but it must + * not warn. + */ +#define _GNU_SOURCE + +#include <errno.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "../wrappers.h" +#include "../utils.h" +#include "../../kselftest_harness.h" + +#ifndef FSCONFIG_CMD_CREATE +#define FSCONFIG_CMD_CREATE 6 +#endif + +/* TAINT_WARN, i.e. bit 9 of /proc/sys/kernel/tainted. */ +#define TAINT_WARN_BIT 9 + +static bool taint_warn_set(void) +{ + unsigned long taint = 0; + FILE *f; + + f = fopen("/proc/sys/kernel/tainted", "r"); + if (!f) + return false; + if (fscanf(f, "%lu", &taint) != 1) + taint = 0; + fclose(f); + + return taint & (1UL << TAINT_WARN_BIT); +} + +static int send_fd(int sock, int fd) +{ + char cmsgbuf[CMSG_SPACE(sizeof(int))] = {}; + char b[1] = { 'x' }; + struct iovec iov = { .iov_base = b, .iov_len = sizeof(b) }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + struct cmsghdr *cmsg; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + + return sendmsg(sock, &msg, 0) < 0 ? -1 : 0; +} + +static int recv_fd(int sock) +{ + char cmsgbuf[CMSG_SPACE(sizeof(int))] = {}; + char b[1]; + struct iovec iov = { .iov_base = b, .iov_len = sizeof(b) }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + struct cmsghdr *cmsg; + int fd = -1; + + if (recvmsg(sock, &msg, 0) <= 0) + return -1; + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS) + return -1; + memcpy(&fd, CMSG_DATA(cmsg), sizeof(int)); + + return fd; +} + +/* + * Create a context for @fsname in a child and complete it here. With @nest + * the child first creates its own user namespace, so that the context is + * created in a descendant of the namespace completing it. The child needs a + * mount namespace of its own as well: fsopen() gates on may_mount(), which + * asks for CAP_SYS_ADMIN in the user namespace owning the caller's mount + * namespace. + * + * Returns the result of FSCONFIG_CMD_CREATE with errno set, or -ENODATA if + * the child could not create the context at all. + */ +static int create_from_child(const char *fsname, bool nest) +{ + int sock[2], fd, ret, status; + pid_t pid; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock)) + return -ENODATA; + + pid = fork(); + if (pid < 0) { + close(sock[0]); + close(sock[1]); + return -ENODATA; + } + + if (pid == 0) { + close(sock[0]); + + if (nest && unshare(CLONE_NEWUSER | CLONE_NEWNS)) + _exit(1); + + fd = sys_fsopen(fsname, 0); + if (fd < 0) + _exit(1); + if (send_fd(sock[1], fd)) + _exit(1); + _exit(0); + } + + close(sock[1]); + fd = recv_fd(sock[0]); + close(sock[0]); + wait_for_pid(pid); + waitpid(pid, &status, WNOHANG); + + if (fd < 0) + return -ENODATA; + + errno = 0; + ret = sys_fsconfig(fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); + status = errno; + close(fd); + errno = status; + + return ret; +} + +FIXTURE(fscontext_ns) { + bool warn_before; +}; + +FIXTURE_SETUP(fscontext_ns) +{ + self->warn_before = taint_warn_set(); + + if (setup_userns() != 0) + SKIP(return, "setup_userns failed"); +} + +FIXTURE_TEARDOWN(fscontext_ns) +{ +} + +/* + * The condition the kernel used to WARN about. It has to be refused, and it + * has to be refused quietly: an unprivileged task reaches this. + */ +FIXTURE_VARIANT(fscontext_ns) { + const char *fsname; + int expected_errno; +}; + +FIXTURE_VARIANT_ADD(fscontext_ns, binfmt_misc) { + .fsname = "binfmt_misc", + .expected_errno = EINVAL, +}; + +FIXTURE_VARIANT_ADD(fscontext_ns, overlay) { + .fsname = "overlay", + .expected_errno = EIO, +}; + +TEST_F(fscontext_ns, create_from_descendant_userns) +{ + int ret; + + ret = create_from_child(variant->fsname, true); + if (ret == -ENODATA) + SKIP(return, "%s unavailable", variant->fsname); + + ASSERT_EQ(-1, ret); + ASSERT_EQ(variant->expected_errno, errno); + + /* + * Only meaningful if nothing had warned before us. Note that an + * unrelated warning racing this test would look like a failure. + */ + if (self->warn_before) + TH_LOG("TAINT_WARN already set, not checking for a new warning"); + else + ASSERT_FALSE(taint_warn_set()); +} + +/* + * The same handover within one user namespace is a supported thing to do and + * has to keep working. binfmt_misc takes no options, so the create succeeds + * outright and this also shows the test really drives the create path. + */ +TEST(create_from_same_userns) +{ + int ret; + + if (setup_userns() != 0) + SKIP(return, "setup_userns failed"); + + ret = create_from_child("binfmt_misc", false); + if (ret == -ENODATA) + SKIP(return, "binfmt_misc unavailable"); + + ASSERT_EQ(0, ret); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile index 612aad69a93a..f47141484275 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -5,6 +5,13 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) TEST_GEN_PROGS := fusectl_test TEST_GEN_FILES := fuse_mnt +# fuse_acl_cache_test requires libfuse3; add it only when the library is present. +ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null) +ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null) +ifneq ($(ACL_CFLAGS),) +TEST_GEN_PROGS += fuse_acl_cache_test +endif + include ../../lib.mk VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null) @@ -19,3 +26,6 @@ endif $(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS) $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS) + +$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS) +$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c new file mode 100644 index 000000000000..2411a6e285f1 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC + * + * A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode + * with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed + * (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls + * forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel, + * __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED, + * inadvertently enabling the kernel ACL cache for that inode. The next + * getxattr populates the cache. Because fuse_set_acl() skips + * forget_all_cached_acls() for !fc->posix_acl mounts, any subsequent change to + * the ACL leaves the stale kernel entry in place, and the next getxattr returns + * wrong data without ever reaching the FUSE daemon. + * + * Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is + * ACL_DONT_CACHE, preserving the "never cache" invariant for the inode's + * lifetime. + * + * Test outline: + * 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated). + * 2. lgetxattr -> daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE). + * 3. statx(AT_STATX_FORCE_SYNC) -> forget_all_cached_acls() called. + * Buggy: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled). + * Fixed: ACL_DONT_CACHE preserved. + * 4. lgetxattr -> daemon called, ACL_A returned. + * Buggy: result now cached (ACL_NOT_CACHED -> cached). + * Fixed: result still not cached. + * 5. Daemon switches to ACL_B internally (different size). + * 6. lgetxattr -> should return ACL_B (44 bytes). + * Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL. + * Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS. + */ + +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <linux/limits.h> +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/xattr.h> +#include <unistd.h> + +#define FUSE_USE_VERSION 31 +#include <fuse_lowlevel.h> + +#include "kselftest_harness.h" + +/* ---- ACL binary encoding ------------------------------------------------ */ +/* + * POSIX ACL v2 xattr format (little-endian): + * header: u32 version (= 0x00000002) + * entry: u16 tag | u16 perm | u32 id + * + * Entries must appear in tag-ascending order; named USER/GROUP entries + * require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation. + */ + +/* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */ +static const uint8_t acl_a[] = { + 0x02, 0x00, 0x00, 0x00, /* v2 header */ + 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */ + 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */ + 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */ +}; + +/* + * ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes. + * A named USER entry requires a MASK; all tags in ascending order. + */ +static const uint8_t acl_b[] = { + 0x02, 0x00, 0x00, 0x00, /* v2 header */ + 0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */ + 0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */ + 0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */ + 0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */ + 0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */ +}; + +/* ---- Shared state (daemon thread <-> test thread) ----------------------- */ + +#define FILE_INO 2 +#define FILE_NAME "testfile" + +struct daemon_state { + pthread_mutex_t lock; + const uint8_t *acl; + size_t acl_size; + int getxattr_count; +}; + +/* + * Global: callbacks are stateless fns so we use a single global. + * Safe because only one test instance runs at a time. + */ +static struct daemon_state g_ds = { + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +/* ---- FUSE lowlevel callbacks -------------------------------------------- */ + +static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + if (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) { + fuse_reply_err(req, ENOENT); + return; + } + struct fuse_entry_param e = {}; + + /* + * Long attr/entry timeouts so that normal stat() calls do not + * expire and trigger forget_all_cached_acls() on their own; + * only the explicit AT_STATX_FORCE_SYNC should trigger it. + */ + e.ino = FILE_INO; + e.generation = 1; + e.attr_timeout = 10.0; + e.entry_timeout = 10.0; + e.attr.st_ino = FILE_INO; + e.attr.st_mode = S_IFREG | 0644; + e.attr.st_nlink = 1; + fuse_reply_entry(req, &e); +} + +static void fs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat st = {}; + + (void)fi; + if (ino == FUSE_ROOT_ID) { + st.st_ino = FUSE_ROOT_ID; + st.st_mode = S_IFDIR | 0755; + st.st_nlink = 2; + } else if (ino == FILE_INO) { + st.st_ino = FILE_INO; + st.st_mode = S_IFREG | 0644; + st.st_nlink = 1; + } else { + fuse_reply_err(req, ENOENT); + return; + } + fuse_reply_attr(req, &st, 10); +} + +static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + if (ino != FILE_INO || + strcmp(name, "system.posix_acl_access") != 0) { + fuse_reply_err(req, ENODATA); + return; + } + + pthread_mutex_lock(&g_ds.lock); + const uint8_t *acl = g_ds.acl; + size_t acl_size = g_ds.acl_size; + g_ds.getxattr_count++; + pthread_mutex_unlock(&g_ds.lock); + + if (size == 0) + fuse_reply_xattr(req, acl_size); + else if (size < acl_size) + fuse_reply_err(req, ERANGE); + else + fuse_reply_buf(req, (const char *)acl, acl_size); +} + +static const struct fuse_lowlevel_ops fs_ops = { + .lookup = fs_lookup, + .getattr = fs_getattr, + .getxattr = fs_getxattr, +}; + +/* ---- Daemon thread ------------------------------------------------------- */ + +static void *run_daemon(void *arg) +{ + fuse_session_loop((struct fuse_session *)arg); + return NULL; +} + +/* ---- kselftest harness --------------------------------------------------- */ + +FIXTURE(acl_cache) { + struct fuse_session *se; + char mountpoint[PATH_MAX]; + char file_path[PATH_MAX]; + pthread_t thread; +}; + +FIXTURE_SETUP(acl_cache) +{ + char *fuse_argv[] = { "fuse_acl_cache_test", NULL }; + struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv); + + g_ds.acl = acl_a; + g_ds.acl_size = sizeof(acl_a); + g_ds.getxattr_count = 0; + + strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX"); + if (!mkdtemp(self->mountpoint)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + + snprintf(self->file_path, sizeof(self->file_path), + "%s/" FILE_NAME, self->mountpoint); + + self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL); + if (!self->se) { + rmdir(self->mountpoint); + SKIP(return, "fuse_session_new failed"); + } + + if (fuse_session_mount(self->se, self->mountpoint)) { + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "fuse_session_mount failed " + "(missing fusermount3 or insufficient privileges)"); + } + + if (pthread_create(&self->thread, NULL, run_daemon, self->se)) { + fuse_session_unmount(self->se); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); + SKIP(return, "pthread_create: %s", strerror(errno)); + } + + fuse_opt_free_args(&args); +} + +FIXTURE_TEARDOWN(acl_cache) +{ + fuse_session_exit(self->se); + fuse_session_unmount(self->se); + pthread_join(self->thread, NULL); + fuse_session_destroy(self->se); + rmdir(self->mountpoint); +} + +static int do_force_statx(const char *path) +{ + struct statx stx; + + return statx(AT_FDCWD, path, AT_STATX_FORCE_SYNC, STATX_BASIC_STATS, + &stx); +} + +TEST_F(acl_cache, stale_after_force_sync) +{ + char buf[512]; + ssize_t sz; + int count; + + /* + * Step 1: two getxattr calls before any statx(FORCE_SYNC). + * i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED, + * sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel + * is never placed and the result is never cached. Both calls must + * reach the daemon, proving ACL_DONT_CACHE suppresses caching. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + ASSERT_EQ(count, 2); + TH_LOG("step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), " + "ACL_DONT_CACHE is working", count); + + /* + * Step 2: statx(AT_STATX_FORCE_SYNC). + * fuse_update_get_attr() calls forget_all_cached_acls() before sending + * FUSE_GETATTR. + * Buggy kernel: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled) + * Fixed kernel: ACL_DONT_CACHE preserved (no effect) + */ + ASSERT_EQ(do_force_statx(self->file_path), 0); + TH_LOG("step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded"); + + /* + * Step 3: getxattr — cache population attempt after the trigger. + * Buggy: *p == ACL_NOT_CACHED -> sentinel placed -> fuse_get_inode_acl + * called -> ACL_A parsed and stored in the kernel cache. + * Fixed: *p == ACL_DONT_CACHE -> sentinel placement skipped -> + * fuse_get_inode_acl called but result not cached. + * Either way the correct ACL_A is returned here. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + ASSERT_EQ(sz, (ssize_t)sizeof(acl_a)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + ASSERT_EQ(count, 3); + TH_LOG("step 3 OK: post-trigger getxattr reached daemon (count=%d), " + "returned correct ACL_A (%zd bytes)", count, sz); + + /* + * Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes). + * Simulates an ACL change that fuse_set_acl() would NOT invalidate for + * !fc->posix_acl mounts (it skips forget_all_cached_acls in that case). + * On a fixed kernel the ACL was never cached, so this is moot. + */ + pthread_mutex_lock(&g_ds.lock); + g_ds.acl = acl_b; + g_ds.acl_size = sizeof(acl_b); + pthread_mutex_unlock(&g_ds.lock); + TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b)); + + /* + * Step 5: getxattr — the decisive check. + * Buggy kernel: cache hit -> stale ACL_A (28 bytes), count stays 3. + * Fixed kernel: no cache -> daemon called -> ACL_B (44 bytes), count 4. + */ + sz = lgetxattr(self->file_path, "system.posix_acl_access", + buf, sizeof(buf)); + + pthread_mutex_lock(&g_ds.lock); + count = g_ds.getxattr_count; + pthread_mutex_unlock(&g_ds.lock); + + if (sz == (ssize_t)sizeof(acl_a)) + TH_LOG("step 5 BUG: stale ACL_A (%zd bytes) from kernel cache " + "(count=%d); ACL_DONT_CACHE corrupted by " + "forget_all_cached_acls()", sz, count); + else + TH_LOG("step 5 OK: daemon reached (count=%d), " + "fresh ACL_B (%zd bytes)", count, sz); + + EXPECT_EQ(sz, (ssize_t)sizeof(acl_b)); + EXPECT_EQ(count, 4); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/idmapped_tmpfile.c b/tools/testing/selftests/filesystems/idmapped_tmpfile.c new file mode 100644 index 000000000000..bc411ab8281e --- /dev/null +++ b/tools/testing/selftests/filesystems/idmapped_tmpfile.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <sched.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/fsuid.h> +#include <sys/stat.h> +#include <sys/syscall.h> + +#include <linux/mount.h> +#include <linux/types.h> + +#include "kselftest_harness.h" +#include "wrappers.h" +#include "utils.h" + +/* + * The test mount maps caller-visible ids [0, MAP_RANGE) onto the on-disk range + * [MAP_HOST, MAP_HOST + MAP_RANGE). An id outside [0, MAP_RANGE) therefore has + * no mapping in the mount and is not representable in the filesystem. + */ +#define MAP_HOST 10000 +#define MAP_RANGE 10000 +#define UNMAPPED 50000 + +#ifndef MOUNT_ATTR_IDMAP +#define MOUNT_ATTR_IDMAP 0x00100000 +#endif + +#ifndef __NR_mount_setattr +#define __NR_mount_setattr 442 +#endif + +static inline int sys_mount_setattr(int dfd, const char *path, + unsigned int flags, + struct mount_attr *attr, size_t size) +{ + return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); +} + +/* + * Clone @path into a detached mount idmapped so that caller-visible ids + * [0, MAP_RANGE) map onto the on-disk ids [MAP_HOST, MAP_HOST + MAP_RANGE). + * Returns the mount fd, or -1 if idmapped mounts are not available. + */ +static int idmapped_clone(const char *path) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int fd_tree, userns_fd, ret; + + fd_tree = sys_open_tree(AT_FDCWD, path, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (fd_tree < 0) + return -1; + + userns_fd = get_userns_fd(MAP_HOST, 0, MAP_RANGE); + if (userns_fd < 0) { + close(fd_tree); + return -1; + } + + attr.userns_fd = userns_fd; + ret = sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr, sizeof(attr)); + close(userns_fd); + if (ret) { + close(fd_tree); + return -1; + } + + return fd_tree; +} + +FIXTURE(idmapped_tmpfile) { + char dir[64]; /* non-idmapped path to the layer directory */ +}; + +FIXTURE_SETUP(idmapped_tmpfile) +{ + /* Private mount namespace so test mounts need no cleanup. */ + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0); + ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0); + + snprintf(self->dir, sizeof(self->dir), "/tmp/d"); + ASSERT_EQ(mkdir(self->dir, 0777), 0); + /* World-writable so an unmapped caller still passes permission(). */ + ASSERT_EQ(chmod(self->dir, 0777), 0); +} + +FIXTURE_TEARDOWN(idmapped_tmpfile) +{ +} + +/* + * A caller whose fsuid/fsgid have no mapping in the idmapped mount must not be + * able to create an O_TMPFILE. Without the check in vfs_tmpfile() the inode + * would be created owned by (uid_t)-1 and could then be linked into the + * namespace. + */ +TEST_F(idmapped_tmpfile, unmapped_caller_is_refused) +{ + int mfd, fd; + + mfd = idmapped_clone(self->dir); + if (mfd < 0) + SKIP(return, "idmapped mounts not supported"); + + /* Become a caller outside the mount's [0, MAP_RANGE) range. */ + setfsgid(UNMAPPED); + setfsuid(UNMAPPED); + ASSERT_EQ(setfsuid(-1), UNMAPPED); + + fd = openat(mfd, ".", O_TMPFILE | O_WRONLY, 0644); + ASSERT_LT(fd, 0); + EXPECT_EQ(errno, EOVERFLOW); + if (fd >= 0) + close(fd); + + EXPECT_EQ(close(mfd), 0); +} + +/* + * A mapped caller can create an O_TMPFILE and link it into the namespace; the + * ownership round-trips through the mount idmap. This is what makes refusing + * the unmapped case above necessary in the first place. + */ +TEST_F(idmapped_tmpfile, mapped_caller_creates_and_links) +{ + char path[PATH_MAX]; + struct stat st; + int mfd, fd; + + mfd = idmapped_clone(self->dir); + if (mfd < 0) + SKIP(return, "idmapped mounts not supported"); + + /* Caller is uid/gid 0, which maps to MAP_HOST through the mount. */ + fd = openat(mfd, ".", O_TMPFILE | O_RDWR, 0600); + ASSERT_GE(fd, 0); + + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_uid, 0); + EXPECT_EQ(st.st_gid, 0); + + /* The tmpfile is linkable: splice it into the directory. */ + ASSERT_EQ(linkat(fd, "", mfd, "linked", AT_EMPTY_PATH), 0); + EXPECT_EQ(close(fd), 0); + + ASSERT_EQ(fstatat(mfd, "linked", &st, 0), 0); + EXPECT_EQ(st.st_uid, 0); + EXPECT_EQ(st.st_gid, 0); + + /* On the underlying, non-idmapped tmpfs it is stored as MAP_HOST. */ + snprintf(path, sizeof(path), "%s/linked", self->dir); + ASSERT_EQ(stat(path, &st), 0); + EXPECT_EQ(st.st_uid, MAP_HOST); + EXPECT_EQ(st.st_gid, MAP_HOST); + + EXPECT_EQ(close(mfd), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore b/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore new file mode 100644 index 000000000000..493fbcf8d9ec --- /dev/null +++ b/tools/testing/selftests/filesystems/mntns_cleanup/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +mntns_cleanup_test diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/Makefile b/tools/testing/selftests/filesystems/mntns_cleanup/Makefile new file mode 100644 index 000000000000..0e09e7030a5c --- /dev/null +++ b/tools/testing/selftests/filesystems/mntns_cleanup/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +TEST_GEN_PROGS := mntns_cleanup_test + +CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c b/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c new file mode 100644 index 000000000000..5209712568b1 --- /dev/null +++ b/tools/testing/selftests/filesystems/mntns_cleanup/mntns_cleanup_test.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "../../kselftest_harness.h" + +FIXTURE(mntns_cleanup) { +}; + +FIXTURE_SETUP(mntns_cleanup) +{ + if (geteuid() != 0) + SKIP(return, "test requires CAP_SYS_ADMIN"); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + rmdir("/mnt_dir"); + ASSERT_EQ(mkdir("/mnt_dir", 0755), 0); + ASSERT_EQ(mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL), 0); + ASSERT_EQ(mkdir("/mnt_dir/hidden", 0755), 0); + ASSERT_EQ(mkdir("/mnt_dir/hidden/secret", 0755), 0); + ASSERT_EQ(mount("tmpfs", "/mnt_dir/hidden", "tmpfs", 0, NULL), 0); +} + +FIXTURE_TEARDOWN(mntns_cleanup) +{ +} + +/* Mounts must stay connected when a mount namespace is cleaned up. */ +TEST_F(mntns_cleanup, keeps_mounts_connected) +{ + int fd, sfd, err; + + fd = open("/mnt_dir", O_PATH | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + + /* Destroy the namespace; the fd keeps /mnt_dir alive. */ + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + + sfd = openat(fd, "hidden/secret", O_RDONLY); + err = errno; + if (sfd >= 0) + close(sfd); + close(fd); + + ASSERT_LT(sfd, 0) + TH_LOG("mount namespace teardown revealed what the overmount covered"); + ASSERT_EQ(err, ENOENT); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/overlayfs/.gitignore b/tools/testing/selftests/filesystems/overlayfs/.gitignore index e23a18c8b37f..077f7a128168 100644 --- a/tools/testing/selftests/filesystems/overlayfs/.gitignore +++ b/tools/testing/selftests/filesystems/overlayfs/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only dev_in_maps set_layers_via_fds +idmapped_mounts diff --git a/tools/testing/selftests/filesystems/overlayfs/Makefile b/tools/testing/selftests/filesystems/overlayfs/Makefile index d3ad4a77db9b..b3185f684add 100644 --- a/tools/testing/selftests/filesystems/overlayfs/Makefile +++ b/tools/testing/selftests/filesystems/overlayfs/Makefile @@ -8,7 +8,9 @@ LOCAL_HDRS += ../wrappers.h log.h TEST_GEN_PROGS := dev_in_maps TEST_GEN_PROGS += set_layers_via_fds +TEST_GEN_PROGS += idmapped_mounts include ../../lib.mk $(OUTPUT)/set_layers_via_fds: ../utils.c +$(OUTPUT)/idmapped_mounts: ../utils.c diff --git a/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c new file mode 100644 index 000000000000..44a75839f4ed --- /dev/null +++ b/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include <fcntl.h> +#include <limits.h> +#include <sched.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/syscall.h> + +#include <linux/mount.h> +#include <linux/types.h> + +#include "kselftest_harness.h" +#include "../wrappers.h" +#include "../utils.h" + +/* + * An idmapping that maps the mount-visible id range [0, ID_RANGE) onto the + * host/overlay-final id range [ID_HOST, ID_HOST + ID_RANGE). Through such an + * idmapped overlay mount, an overlay-final id of ID_HOST + n is reported as n, + * and an id of n requested through the mount is stored as ID_HOST + n. + */ +#define ID_NS 0 +#define ID_HOST 10000 +#define ID_RANGE 10000 + +/* + * For the composition test the lower layer's on-disk ids live in a + * separate range and are mapped by an idmapped lower layer onto the + * overlay-final range [ID_HOST, ID_HOST + ID_RANGE). + */ +#define LAYER_HOST 20000 + +#ifndef MOUNT_ATTR_IDMAP +#define MOUNT_ATTR_IDMAP 0x00100000 +#endif + +#ifndef __NR_mount_setattr +#define __NR_mount_setattr 442 +#endif + +static inline int sys_mount_setattr(int dfd, const char *path, + unsigned int flags, + struct mount_attr *attr, size_t size) +{ + return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); +} + +static bool ovl_supported(void) +{ + int fd = sys_fsopen("overlay", 0); + + if (fd < 0) + return false; + close(fd); + return true; +} + +/* base/{l,u,w} owned by ID_HOST so they map to ID_NS through the idmap. */ +static int setup_layers(const char *base) +{ + static const char *sub[] = { "", "/l", "/u", "/w" }; + char path[PATH_MAX]; + + for (size_t i = 0; i < ARRAY_SIZE(sub); i++) { + snprintf(path, sizeof(path), "%s%s", base, sub[i]); + if (mkdir(path, 0755) && errno != EEXIST) + return -1; + if (i && chown(path, ID_HOST, ID_HOST)) + return -1; + } + return 0; +} + +static int ovl_mount(const char *base, bool nfs_export) +{ + char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX]; + int fsfd, ovl; + + snprintf(lower, sizeof(lower), "%s/l", base); + snprintf(upper, sizeof(upper), "%s/u", base); + snprintf(work, sizeof(work), "%s/w", base); + + fsfd = sys_fsopen("overlay", 0); + if (fsfd < 0) + return -1; + + if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "lowerdir", lower, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0)) + goto err; + if (nfs_export && + (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "index", "on", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "nfs_export", "on", 0))) + goto err; + if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto err; + + ovl = sys_fsmount(fsfd, 0, 0); + close(fsfd); + return ovl; +err: + close(fsfd); + return -1; +} + +/* Idmap the (still detached, not yet visible) overlay mount @mfd. */ +static int ovl_idmap(int mfd) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int ret, userns_fd; + + /* + * get_userns_fd(fs_id, mount_id, range): a file whose filesystem id + * is fs_id + n is shown through the idmapped mount as mount_id + n. + * Here the overlay-final (fs side) range is [ID_HOST, ..) and the + * caller-visible (mount side) range is [ID_NS, ..). + */ + userns_fd = get_userns_fd(ID_HOST, ID_NS, ID_RANGE); + if (userns_fd < 0) + return -1; + + attr.userns_fd = userns_fd; + ret = sys_mount_setattr(mfd, "", AT_EMPTY_PATH, &attr, sizeof(attr)); + close(userns_fd); + return ret; +} + +/* Clone @path into a detached, idmapped mount usable as an overlay layer. */ +static int idmapped_layer_fd(const char *path, int nsid, int hostid, int range) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int fd_tree, userns_fd; + + fd_tree = sys_open_tree(AT_FDCWD, path, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (fd_tree < 0) + return -1; + userns_fd = get_userns_fd(nsid, hostid, range); + if (userns_fd < 0) { + close(fd_tree); + return -1; + } + attr.userns_fd = userns_fd; + if (sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr, + sizeof(attr))) { + close(userns_fd); + close(fd_tree); + return -1; + } + close(userns_fd); + return fd_tree; +} + +/* Overlay with a layer passed by fd (idmapped) plus a plain upper/work. */ +static int ovl_mount_lower_fd(const char *upper, const char *work, int fd_lower) +{ + int fsfd, ovl; + + fsfd = sys_fsopen("overlay", 0); + if (fsfd < 0) + return -1; + + if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0) || + sys_fsconfig(fsfd, FSCONFIG_SET_FD, "lowerdir+", NULL, fd_lower) || + sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto err; + + ovl = sys_fsmount(fsfd, 0, 0); + close(fsfd); + return ovl; +err: + close(fsfd); + return -1; +} + +/* + * Mount an overlay inside user namespace @u1 (so the overlay sb's s_user_ns is + * not the initial namespace) and idmap that overlay mount with @u2. Runs in a + * child that joins @u1; returns 0 on success. + */ +static int userns_overlay_child(int u1) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + struct stat st; + int ovl, u2; + + /* Become root in the overlay sb's user namespace u1. */ + if (!switch_userns(u1, 0, 0, false)) + return fprintf(stderr, "userns: switch_userns: %m\n"), -1; + if (unshare(CLONE_NEWNS) || + sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL)) + return fprintf(stderr, "userns: unshare/slave: %m\n"), -1; + if (sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL)) + return fprintf(stderr, "userns: mount tmpfs: %m\n"), -1; + if (setup_layers("/tmp/ovl")) + return fprintf(stderr, "userns: setup_layers: %m\n"), -1; + if (mknod("/tmp/ovl/l/file", S_IFREG | 0644, 0) || + chown("/tmp/ovl/l/file", ID_HOST + 5, ID_HOST + 5)) + return fprintf(stderr, "userns: lower file: %m\n"), -1; + + ovl = ovl_mount("/tmp/ovl", false); + if (ovl < 0) + return fprintf(stderr, "userns: ovl_mount: %m\n"), -1; + + /* + * mount_setattr() requires CAP_SYS_ADMIN over the idmap user + * namespace, so it must be a child of u1. Create it now, from + * inside u1. + */ + u2 = get_userns_fd(ID_HOST, ID_NS, ID_RANGE); + if (u2 < 0) + return fprintf(stderr, "userns: get_userns_fd: %m\n"), -1; + attr.userns_fd = u2; + if (sys_mount_setattr(ovl, "", AT_EMPTY_PATH, &attr, sizeof(attr))) + return fprintf(stderr, "userns: mount_setattr: %m\n"), -1; + close(u2); + + if (fstatat(ovl, "file", &st, 0)) + return fprintf(stderr, "userns: fstatat: %m\n"), -1; + if (st.st_uid != ID_NS + 5 || st.st_gid != ID_NS + 5) { + fprintf(stderr, "userns: got %u:%u expected %u:%u\n", + st.st_uid, st.st_gid, ID_NS + 5, ID_NS + 5); + return -1; + } + return 0; +} + +FIXTURE(idmapped_overlay) { + char base[64]; +}; + +FIXTURE_SETUP(idmapped_overlay) +{ + /* Private mount namespace so test mounts need no cleanup. */ + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0); + + /* tmpfs for the layers so we can chown them to arbitrary ids. */ + ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0); + + snprintf(self->base, sizeof(self->base), "/tmp/ovl"); + ASSERT_EQ(setup_layers(self->base), 0); +} + +FIXTURE_TEARDOWN(idmapped_overlay) +{ +} + +/* A file owned by ID_HOST + 5 is reported as ID_NS + 5 through the idmap. */ +TEST_F(idmapped_overlay, getattr) +{ + char path[PATH_MAX]; + struct stat st; + int ovl; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, ID_HOST + 5, ID_HOST + 5), 0); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 5); + EXPECT_EQ(st.st_gid, ID_NS + 5); + + EXPECT_EQ(close(ovl), 0); +} + +/* + * Every creation path initializes the new owner through the mount idmap: + * created as caller id ID_NS, stored on the upper layer as overlay-final + * ID_HOST. Covers ovl_create() (regular file), ovl_mkdir(), ovl_mknod() + * and ovl_symlink() (which share ovl_create_object()), plus the separate + * ovl_tmpfile() path. + */ +TEST_F(idmapped_overlay, create) +{ + static const char *names[] = { "reg", "dir", "fifo", "lnk" }; + char path[PATH_MAX]; + struct stat st; + int ovl, fd; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + /* One object per creation operation, all as caller id ID_NS. */ + fd = openat(ovl, "reg", O_CREAT | O_WRONLY | O_EXCL, 0644); + ASSERT_GE(fd, 0); + EXPECT_EQ(close(fd), 0); + ASSERT_EQ(mkdirat(ovl, "dir", 0755), 0); + ASSERT_EQ(mknodat(ovl, "fifo", S_IFIFO | 0644, 0), 0); + ASSERT_EQ(symlinkat("target", ovl, "lnk"), 0); + + for (size_t i = 0; i < ARRAY_SIZE(names); i++) { + /* Reported as ID_NS through the idmapped mount ... */ + ASSERT_EQ(fstatat(ovl, names[i], &st, AT_SYMLINK_NOFOLLOW), 0); + EXPECT_EQ(st.st_uid, ID_NS); + EXPECT_EQ(st.st_gid, ID_NS); + /* ... and stored as ID_HOST on the upper layer. */ + snprintf(path, sizeof(path), "%s/u/%s", self->base, names[i]); + ASSERT_EQ(lstat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST); + EXPECT_EQ(st.st_gid, ID_HOST); + } + + /* O_TMPFILE goes through the separate ovl_tmpfile() path. */ + fd = openat(ovl, ".", O_TMPFILE | O_WRONLY, 0644); + ASSERT_GE(fd, 0); + /* Inside the mount: caller id ID_NS. */ + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_uid, ID_NS); + EXPECT_EQ(st.st_gid, ID_NS); + /* Link it in so the upper backing file can be inspected too. */ + ASSERT_EQ(linkat(fd, "", ovl, "tmp", AT_EMPTY_PATH), 0); + EXPECT_EQ(close(fd), 0); + snprintf(path, sizeof(path), "%s/u/tmp", self->base); + ASSERT_EQ(lstat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST); + EXPECT_EQ(st.st_gid, ID_HOST); + + EXPECT_EQ(close(ovl), 0); +} + +/* chown through the idmapped mount round-trips: ID_NS + 5 <-> ID_HOST + 5. */ +TEST_F(idmapped_overlay, chown) +{ + char path[PATH_MAX]; + struct stat st; + int ovl, fd; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + ovl = ovl_mount(self->base, false); + ASSERT_GE(ovl, 0); + ASSERT_EQ(ovl_idmap(ovl), 0); + + fd = openat(ovl, "f", O_CREAT | O_WRONLY | O_EXCL, 0644); + ASSERT_GE(fd, 0); + EXPECT_EQ(close(fd), 0); + + ASSERT_EQ(fchownat(ovl, "f", ID_NS + 5, ID_NS + 5, 0), 0); + + ASSERT_EQ(fstatat(ovl, "f", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 5); + EXPECT_EQ(st.st_gid, ID_NS + 5); + + snprintf(path, sizeof(path), "%s/u/f", self->base); + ASSERT_EQ(stat(path, &st), 0); + EXPECT_EQ(st.st_uid, ID_HOST + 5); + EXPECT_EQ(st.st_gid, ID_HOST + 5); + + EXPECT_EQ(close(ovl), 0); +} + +/* + * Composition: an idmapped lower layer underneath an idmapped overlay mount. + * An on-disk id is mapped by the layer idmap into the overlay-final range and + * then by the mount idmap into the caller's range: + * + * on-disk LAYER_HOST+7 --layer--> ID_HOST+7 --mount--> ID_NS+7 + */ +TEST_F(idmapped_overlay, composition) +{ + char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX], path[PATH_MAX]; + struct stat st; + int ovl, fd_lower; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(lower, sizeof(lower), "%s/l", self->base); + snprintf(upper, sizeof(upper), "%s/u", self->base); + snprintf(work, sizeof(work), "%s/w", self->base); + + /* Put the lower layer's ids in the on-disk [LAYER_HOST, ..) range. */ + ASSERT_EQ(chown(lower, LAYER_HOST, LAYER_HOST), 0); + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, LAYER_HOST + 7, LAYER_HOST + 7), 0); + + /* Idmapped lower: on-disk LAYER_HOST <-> overlay-final ID_HOST. */ + fd_lower = idmapped_layer_fd(lower, LAYER_HOST, ID_HOST, ID_RANGE); + ASSERT_GE(fd_lower, 0); + + ovl = ovl_mount_lower_fd(upper, work, fd_lower); + ASSERT_GE(ovl, 0); + EXPECT_EQ(close(fd_lower), 0); + + /* Idmap the overlay mount: overlay-final ID_HOST <-> caller ID_NS. */ + ASSERT_EQ(ovl_idmap(ovl), 0); + + ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0); + EXPECT_EQ(st.st_uid, ID_NS + 7); + EXPECT_EQ(st.st_gid, ID_NS + 7); + + EXPECT_EQ(close(ovl), 0); +} + +/* An idmapped overlay mount whose sb lives inside a user namespace. */ +TEST_F(idmapped_overlay, userns) +{ + int u1; + pid_t pid; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + /* u1 backs the overlay sb: identity-mapped, but not the init ns. */ + u1 = get_userns_fd(0, 0, 65536); + if (u1 < 0) + SKIP(return, "user namespaces not available"); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + int ret = userns_overlay_child(u1); + + _exit(ret ? EXIT_FAILURE : EXIT_SUCCESS); + } + EXPECT_EQ(wait_for_pid(pid), 0); + + EXPECT_EQ(close(u1), 0); +} + +/* + * An nfs_export overlay can be idmapped, and decodable file handles round-trip + * through the idmapped mount with correctly mapped ownership. Overlay file + * handles encode object identity, not ownership, so the mount idmap does not + * affect them; it only maps the owner reported once a handle is reopened. + */ +TEST_F(idmapped_overlay, nfs_export_handles) +{ + char path[PATH_MAX], mnt[128]; + union { + struct file_handle fh; + char buf[sizeof(struct file_handle) + MAX_HANDLE_SZ]; + } fhu; + struct file_handle *fh = &fhu.fh; + struct stat st; + int ovl, mfd, fd, mount_id; + + if (!ovl_supported()) + SKIP(return, "overlayfs not supported"); + + snprintf(path, sizeof(path), "%s/l/file", self->base); + ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown(path, ID_HOST + 7, ID_HOST + 7), 0); + + /* nfs_export=on gives decodable overlay file handles. */ + ovl = ovl_mount(self->base, true); + if (ovl < 0) + SKIP(return, "overlayfs nfs_export not supported"); + ASSERT_EQ(ovl_idmap(ovl), 0); + + /* Attach the idmapped mount so handles can be resolved against it. */ + snprintf(mnt, sizeof(mnt), "%s/mnt", self->base); + ASSERT_EQ(mkdir(mnt, 0755), 0); + ASSERT_EQ(sys_move_mount(ovl, "", AT_FDCWD, mnt, + MOVE_MOUNT_F_EMPTY_PATH), 0); + + snprintf(path, sizeof(path), "%s/file", mnt); + fh->handle_bytes = MAX_HANDLE_SZ; + ASSERT_EQ(name_to_handle_at(AT_FDCWD, path, fh, &mount_id, 0), 0); + + mfd = open(mnt, O_RDONLY | O_DIRECTORY); + ASSERT_GE(mfd, 0); + fd = open_by_handle_at(mfd, fh, O_RDONLY); + EXPECT_EQ(close(mfd), 0); + ASSERT_GE(fd, 0); + + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_uid, ID_NS + 7); + EXPECT_EQ(st.st_gid, ID_NS + 7); + + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(close(ovl), 0); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c index 3c0b93183348..7a293544233d 100644 --- a/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c +++ b/tools/testing/selftests/filesystems/overlayfs/set_layers_via_fds.c @@ -624,7 +624,7 @@ TEST_F(set_layers_via_fds, set_layers_via_detached_mount_fds) ASSERT_EQ(sys_move_mount(fd_tmpfs, "", -EBADF, "/set_layers_via_fds_tmpfs", MOVE_MOUNT_F_EMPTY_PATH), 0); - fd_tmp = open_tree(fd_tmpfs, "u", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + fd_tmp = sys_open_tree(fd_tmpfs, "u", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(fd_tmp, 0); layer_fds[0] = openat(fd_tmp, "upper", O_CLOEXEC | O_DIRECTORY | O_PATH); @@ -633,25 +633,25 @@ TEST_F(set_layers_via_fds, set_layers_via_detached_mount_fds) layer_fds[1] = openat(fd_tmp, "work", O_CLOEXEC | O_DIRECTORY | O_PATH); ASSERT_GE(layer_fds[1], 0); - layer_fds[2] = open_tree(fd_tmpfs, "l1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[2] = sys_open_tree(fd_tmpfs, "l1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[2], 0); - layer_fds[3] = open_tree(fd_tmpfs, "l2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[3] = sys_open_tree(fd_tmpfs, "l2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[3], 0); - layer_fds[4] = open_tree(fd_tmpfs, "l3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[4] = sys_open_tree(fd_tmpfs, "l3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[4], 0); - layer_fds[5] = open_tree(fd_tmpfs, "l4", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[5] = sys_open_tree(fd_tmpfs, "l4", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[5], 0); - layer_fds[6] = open_tree(fd_tmpfs, "d1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[6] = sys_open_tree(fd_tmpfs, "d1", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[6], 0); - layer_fds[7] = open_tree(fd_tmpfs, "d2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[7] = sys_open_tree(fd_tmpfs, "d2", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[7], 0); - layer_fds[8] = open_tree(fd_tmpfs, "d3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + layer_fds[8] = sys_open_tree(fd_tmpfs, "d3", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); ASSERT_GE(layer_fds[8], 0); ASSERT_EQ(close(fd_tmpfs), 0); diff --git a/tools/testing/selftests/filesystems/statmount/statmount_test.c b/tools/testing/selftests/filesystems/statmount/statmount_test.c index 8dc018d47a93..60c2c544db6a 100644 --- a/tools/testing/selftests/filesystems/statmount/statmount_test.c +++ b/tools/testing/selftests/filesystems/statmount/statmount_test.c @@ -82,6 +82,9 @@ static void cleanup_namespace(void) { int ret; + if (f_mountinfo) + fclose(f_mountinfo); + ret = fchdir(orig_root); if (ret == -1) ksft_perror("fchdir to original root"); @@ -515,7 +518,7 @@ static void test_statmount_mnt_opts(void) return; } - ksft_test_result_fail("didnt't find mount entry\n"); + ksft_test_result_fail("didn't find mount entry\n"); free(sm); free(line); } diff --git a/tools/testing/selftests/filesystems/ustat_test.c b/tools/testing/selftests/filesystems/ustat_test.c new file mode 100644 index 000000000000..d429fd18d779 --- /dev/null +++ b/tools/testing/selftests/filesystems/ustat_test.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test ustat(2): looking up superblocks by device number. + * + * ustat() resolves a device number to a mounted superblock via + * user_get_super(). Check that the device number of a mounted tmpfs (an + * anonymous device) resolves, that it stops resolving once the filesystem + * is unmounted and that bogus device numbers report EINVAL. + */ +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "../kselftest_harness.h" + +/* struct ustat is not exported through UAPI, mirror include/linux/types.h. */ +struct ustat_buf { + int f_tfree; + unsigned long f_tinode; + char f_fname[6]; + char f_fpack[6]; + /* slack in case an architecture lays the struct out differently */ + char pad[64]; +}; + +#ifdef __NR_ustat + +/* + * The kernel decodes @dev with new_decode_dev(), which matches the low 32 + * bits of the st_dev encoding stat(2) returns for any major below 4096. + */ +static int sys_ustat(unsigned int dev, struct ustat_buf *buf) +{ + return syscall(__NR_ustat, dev, buf); +} + +static int write_string(const char *path, const char *string) +{ + ssize_t len = strlen(string); + int fd; + + fd = open(path, O_WRONLY); + if (fd < 0) + return -1; + if (write(fd, string, len) != len) { + close(fd); + return -1; + } + return close(fd); +} + +/* Enter namespaces in which mounting a tmpfs instance is allowed. */ +static int setup_namespaces(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + char map[64]; + + if (unshare(CLONE_NEWNS | (uid ? CLONE_NEWUSER : 0))) + return -1; + + if (uid) { + if (write_string("/proc/self/setgroups", "deny")) + return -1; + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_string("/proc/self/uid_map", map)) + return -1; + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_string("/proc/self/gid_map", map)) + return -1; + } + + return mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL); +} + +TEST(resolves_mounted_superblock) +{ + char dir[] = "/tmp/ustat_test.XXXXXX"; + struct ustat_buf ub; + struct stat st; + + ASSERT_NE(NULL, mkdtemp(dir)); + + if (setup_namespaces()) { + rmdir(dir); + SKIP(return, "cannot set up namespaces: %s", strerror(errno)); + } + + ASSERT_EQ(0, mount("ustat_test", dir, "tmpfs", 0, NULL)); + ASSERT_EQ(0, stat(dir, &st)); + + memset(&ub, 0xff, sizeof(ub)); + ASSERT_EQ(0, sys_ustat(st.st_dev, &ub)) + TH_LOG("ustat(%u): %s", (unsigned int)st.st_dev, + strerror(errno)); + + ASSERT_EQ(0, umount(dir)); + + /* The unmount removed the superblock, the device is gone. */ + ASSERT_EQ(-1, sys_ustat(st.st_dev, &ub)); + ASSERT_EQ(EINVAL, errno); + + rmdir(dir); +} + +TEST(bogus_device_numbers) +{ + struct ustat_buf ub; + + ASSERT_EQ(-1, sys_ustat(0, &ub)); + ASSERT_EQ(EINVAL, errno); + + /* major 4095, minor 1048575: nothing plausible lives there */ + ASSERT_EQ(-1, sys_ustat((0xfffu << 8) | 0xffu | (0xfff00u << 12), &ub)); + ASSERT_EQ(EINVAL, errno); +} + +#else /* !__NR_ustat */ + +TEST(unsupported) +{ + SKIP(return, "ustat(2) is not available on this architecture"); +} + +#endif /* __NR_ustat */ + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/ftrace/ftracetest b/tools/testing/selftests/ftrace/ftracetest index 0a56bf209f6c..8ad2c385407e 100755 --- a/tools/testing/selftests/ftrace/ftracetest +++ b/tools/testing/selftests/ftrace/ftracetest @@ -503,6 +503,7 @@ for t in $TEST_CASES; do done # Test on instance loop +(cd $TRACING_DIR; initialize_system) INSTANCE=" (instance) " for t in $TEST_CASES; do test_on_instance $t || continue diff --git a/tools/testing/selftests/ftrace/poll.c b/tools/testing/selftests/ftrace/poll.c index 53258f7515e7..4da86a20dc85 100644 --- a/tools/testing/selftests/ftrace/poll.c +++ b/tools/testing/selftests/ftrace/poll.c @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) } close(pfd.fd); - /* If timeout happned (ret == 0), exit code is 1 */ + /* If timeout happened (ret == 0), exit code is 1 */ if (ret == 0) return 1; diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc index c300eb020262..e2322693d0c3 100644 --- a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_eprobe.tc @@ -1,16 +1,16 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 # description: Generic dynamic event - add/remove eprobe events -# requires: dynamic_events events/syscalls/sys_enter_openat "<attached-group>.<attached-event> [<args>]":README +# requires: dynamic_events events/syscalls/sys_enter_chdir "<attached-group>.<attached-event> [<args>]":README echo 0 > events/enable clear_dynamic_events SYSTEM="syscalls" -EVENT="sys_enter_openat" +EVENT="sys_enter_chdir" FIELD="filename" -EPROBE="eprobe_open" +EPROBE="eprobe_chdir" OPTIONS="file=+0(\$filename):ustring" echo "e:$EPROBE $SYSTEM/$EVENT $OPTIONS" >> dynamic_events @@ -18,20 +18,14 @@ grep -q "$EPROBE" dynamic_events test -d events/eprobes/$EPROBE echo 1 > events/eprobes/$EPROBE/enable -ls +cd /sys/kernel/tracing echo 0 > events/eprobes/$EPROBE/enable -content=`grep '^ *ls-' trace | grep 'file='` -nocontent=`grep '^ *ls-' trace | grep 'file=' | grep -v -e '"/' -e '"."' -e '(fault)' ` || true - +content=`grep -e 'file="/sys/kernel/tracing"\|(fault)' trace` if [ -z "$content" ]; then exit_fail fi -if [ ! -z "$nocontent" ]; then - exit_fail -fi - echo "-:$EPROBE" >> dynamic_events ! grep -q "$EPROBE" dynamic_events diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc index 2915206777b6..89660a9adf44 100644 --- a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_fprobe_module.tc @@ -16,23 +16,32 @@ echo > dynamic_events FUNC1='foo_bar*' FUNC2='vfs_read' +:;: "Save enabled functions count" ;: +ecount=`cat enabled_functions | wc -l` + +count_enabled_functions() { + count=`cat enabled_functions | wc -l` + count=$(($count-$ecount)) + echo $count +} + :;: "Add an event on the test module" ;: echo "f:test1 $FUNC1" >> dynamic_events echo 1 > events/fprobes/test1/enable :;: "Ensure it is enabled" ;: -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -ne 0 :;: "Check the enabled_functions is cleared on unloading" ;: rmmod trace-events-sample -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -eq 0 :;: "Check it is kept clean" ;: modprobe trace-events-sample echo 1 > events/fprobes/test1/enable || echo "OK" -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -eq 0 :;: "Add another event not on the test module" ;: @@ -40,19 +49,19 @@ echo "f:test2 $FUNC2" >> dynamic_events echo 1 > events/fprobes/test2/enable :;: "Ensure it is enabled" ;: -ofuncs=`cat enabled_functions | wc -l` +ofuncs=`count_enabled_functions` test $ofuncs -ne 0 :;: "Disable and remove the first event" echo 0 > events/fprobes/test1/enable echo "-:fprobes/test1" >> dynamic_events -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $ofuncs -eq $funcs :;: "Disable and remove other events" ;: echo 0 > events/fprobes/enable echo > dynamic_events -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -eq 0 rmmod trace-events-sample @@ -63,12 +72,12 @@ echo "f:test1 $FUNC1" >> dynamic_events echo 1 > events/fprobes/test1/enable echo "f:test2 $FUNC2" >> dynamic_events echo 1 > events/fprobes/test2/enable -ofuncs=`cat enabled_functions | wc -l` +ofuncs=`count_enabled_functions` test $ofuncs -ne 0 :;: "Unload module (ftrace entry should be removed)" ;: rmmod trace-events-sample -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -ne 0 test $ofuncs -ne $funcs @@ -77,7 +86,7 @@ echo 0 > events/fprobes/test2/enable echo "-:fprobes/test2" >> dynamic_events :;: "Ensure ftrace is disabled." ;: -funcs=`cat enabled_functions | wc -l` +funcs=`count_enabled_functions` test $funcs -eq 0 echo 0 > events/fprobes/enable diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc index f2048c244526..19430bd5864c 100644 --- a/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc +++ b/tools/testing/selftests/ftrace/test.d/dynevent/add_remove_uprobe.tc @@ -12,9 +12,32 @@ echo 0 > events/enable echo > dynamic_events REALBIN=`readlink -f /bin/sh` -ENTRYPOINT=`readelf -h ${REALBIN} | grep Entry | sed -e 's/[^0]*//'` -echo "p:myevent ${REALBIN}:${ENTRYPOINT}" >> uprobe_events +# Get the entry point virtual address from ELF header +ENTRY=`readelf -hW ${REALBIN} | grep "Entry point" | awk '{print $NF}'` + +# Convert virtual address to file offset: find the LOAD segment containing +# the entry point, then compute file_offset = e_entry - p_vaddr + p_offset. +# For PIE binaries this is a no-op (vaddr == file offset), but for non-PIE +# executables the virtual address is much larger than the file size and +# must be converted, otherwise uprobe_register() rejects it with -EINVAL. +ENTRY_DEC=$(printf '%d' "$ENTRY") +OFFSET=$ENTRY +while IFS= read -r line; do + set -- $line + [ "$1" = "LOAD" ] || continue + VA_DEC=$(printf '%d' "$3") + OFF_DEC=$(printf '%d' "$2") + FSZ_DEC=$(printf '%d' "$5") + if [ "$ENTRY_DEC" -ge "$VA_DEC" ] && [ "$ENTRY_DEC" -lt "$((VA_DEC + FSZ_DEC))" ]; then + OFFSET=$(printf '0x%x' "$((ENTRY_DEC - VA_DEC + OFF_DEC))") + break + fi +done << EOF +$(readelf -lW ${REALBIN} | grep LOAD) +EOF + +echo "p:myevent ${REALBIN}:${OFFSET}" >> uprobe_events grep -q myevent uprobe_events test -d events/uprobes/myevent diff --git a/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc b/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc index 8d275e3238d9..04eb8546fc07 100644 --- a/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc +++ b/tools/testing/selftests/ftrace/test.d/trigger/trigger-hist-poll.tc @@ -1,7 +1,7 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 # description: event trigger - test poll wait on histogram -# requires: set_event events/sched/sched_process_free/trigger events/sched/sched_process_free/hist +# requires: set_event events/sched/sched_process_exit/trigger events/sched/sched_process_exit/hist # flags: instance POLL=${FTRACETEST_ROOT}/poll @@ -11,7 +11,7 @@ if [ ! -x ${POLL} ]; then exit_unresolved fi -EVENT=events/sched/sched_process_free/ +EVENT=events/sched/sched_process_exit/ # Check poll ops is supported. Before implementing poll on hist file, it # returns soon with POLLIN | POLLOUT, but not POLLPRI. diff --git a/tools/testing/selftests/futex/functional/.gitignore b/tools/testing/selftests/futex/functional/.gitignore index 23b9fea8d190..7c39d10b38e4 100644 --- a/tools/testing/selftests/futex/functional/.gitignore +++ b/tools/testing/selftests/futex/functional/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +futex_lock_pi_exiting futex_numa_mpol futex_priv_hash futex_requeue diff --git a/tools/testing/selftests/futex/functional/Makefile b/tools/testing/selftests/futex/functional/Makefile index 5c1c824f9740..a03bd5acba50 100644 --- a/tools/testing/selftests/futex/functional/Makefile +++ b/tools/testing/selftests/futex/functional/Makefile @@ -11,7 +11,8 @@ endif LOCAL_HDRS := \ ../include/futextest.h \ - ../include/atomic.h + ../include/atomic.h \ + ../include/futex_thread.h TEST_GEN_PROGS := \ futex_wait_timeout \ futex_wait_wouldblock \ @@ -26,7 +27,8 @@ TEST_GEN_PROGS := \ futex_numa_mpol \ futex_waitv \ futex_numa \ - robust_list + robust_list \ + futex_lock_pi_exiting TEST_PROGS := run.sh diff --git a/tools/testing/selftests/futex/functional/futex_lock_pi_exiting.c b/tools/testing/selftests/futex/functional/futex_lock_pi_exiting.c new file mode 100644 index 000000000000..623c5f3a1836 --- /dev/null +++ b/tools/testing/selftests/futex/functional/futex_lock_pi_exiting.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/****************************************************************************** + * + * futex_lock_pi_exiting.c + * + * Coverage for the FUTEX_LOCK_PI owner-exiting path. futex_wait_timeout.c + * already covers FUTEX_LOCK_PI timeout semantics and robust_list.c covers + * owner death via the robust list, but nothing exercises FUTEX_LOCK_PI when a + * non-robust PI owner exits while holding the lock, nor the basic ownership / + * EDEADLK / unlock word semantics. + * + * DESCRIPTION + * Three tests: + * + * 1. lock_unlock_basic - uncontended FUTEX_LOCK_PI semantics: the futex + * word carries the owner TID, a recursive lock by the owner returns + * EDEADLK, and FUTEX_UNLOCK_PI clears the word. + * + * 2. owner_dies_with_blocked_waiter - a thread acquires a PI futex and + * exits while holding it. do_exit() runs futex_cleanup_begin() (which + * flips the task's futex state to FUTEX_STATE_EXITING) and + * exit_pi_state_list() (which hands off / tears down the pi_state). A + * contending FUTEX_LOCK_PI waiter must end up in one of: + * + * 0 - ownership was transferred to / acquired by the waiter + * EOWNERDEAD - previous owner died holding the lock; the caller is + * now the owner and must acknowledge by unlocking + * ESRCH - the owner encoded in the futex word is already gone + * + * and on the first two it must actually own the lock afterwards. + * + * 3. stress_owner_exits - hammer that same exiting-owner path. This is + * where the following bug lived: the 'exiting' task pointer was not + * reset at the retry label, so after wait_for_owner_exiting() dropped + * its reference a subsequent retry that returned a non-EBUSY error fed + * the stale pointer back in and tripped WARN_ON_ONCE(exiting). That + * warning is invisible to user space, so this test cannot observe it + * through a syscall return value; it only becomes a visible failure + * (crash) on a kernel booted with panic_on_warn=1 (or built with + * CONFIG_BUG_ON_DATA_CORRUPTION). The loop drives the path so that + * such a kernel trips on it - the canonical way fuzz/CI catch these. + * + * Fix: 210d36d892de ("futex: Clear stale exiting pointer in + * futex_lock_pi() retry path") + * Fixes: 3ef240eaff36 ("futex: Prevent exit livelock") + * + * AUTHOR + * Based on futex test boilerplate by Darren Hart <dvhart@linux.intel.com> + * + *****************************************************************************/ + +#define _GNU_SOURCE + +#include <errno.h> +#include <pthread.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <sys/syscall.h> + +#include "futextest.h" +#include "kselftest_harness.h" + +/* + * Iterations for the stress variant. Enough to repeatedly land in the narrow + * EXITING window while keeping the test fast. + */ +#define STRESS_ITERS 1000 + +static futex_t pi_futex; +static pthread_barrier_t locked_barrier; +static pthread_barrier_t release_barrier; + +static pid_t sys_gettid(void) +{ + return syscall(SYS_gettid); +} + +/* + * Owner thread: acquire the PI futex and exit while still holding it. Two + * modes: + * park == 0: signal that we hold the lock, then exit immediately (racy; the + * waiter races against our exit path). + * park == 1: signal that we hold the lock and keep holding until released + * via release_barrier, so a waiter has time to contend as a real + * PI waiter before we die. + */ +static void *owner_thread(void *arg) +{ + long park = (long)arg; + + if (futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG) != 0) + return (void *)(intptr_t)-errno; + + pthread_barrier_wait(&locked_barrier); + + if (park) + pthread_barrier_wait(&release_barrier); + + /* Die while still holding the lock. */ + pthread_exit((void *)0); +} + +/* + * Block on the PI futex as a waiter. Returns 0 on acquisition, otherwise the + * positive errno. + */ +static int waiter_lock_pi(void) +{ + int ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG); + + return ret == 0 ? 0 : errno; +} + +static int outcome_ok(int outcome) +{ + return outcome == 0 || outcome == EOWNERDEAD || outcome == ESRCH; +} + +/* Results published by waiter_thread() for the owning thread to assert on. */ +static int waiter_outcome; +static int waiter_owns; + +/* + * Waiter thread for the blocked-waiter test. Contends for the lock and, when + * it acquires, records whether the futex word actually carries its TID and + * releases the lock itself (FUTEX_UNLOCK_PI must run in the owning thread). + */ +static void *waiter_thread(void *arg) +{ + pid_t tid = sys_gettid(); + + waiter_outcome = waiter_lock_pi(); + if (waiter_outcome == 0 || waiter_outcome == EOWNERDEAD) { + waiter_owns = (pi_futex & FUTEX_TID_MASK) == (futex_t)tid; + futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG); + } + return NULL; +} + +FIXTURE(lock_pi_exiting) { +}; + +FIXTURE_SETUP(lock_pi_exiting) { +} + +FIXTURE_TEARDOWN(lock_pi_exiting) { +} + +/* + * Uncontended FUTEX_LOCK_PI semantics, fully deterministic. + */ +TEST_F(lock_pi_exiting, lock_unlock_basic) +{ + pid_t tid = sys_gettid(); + int ret; + + pi_futex = FUTEX_INITIALIZER; + + /* Acquire: we become the owner, our TID lands in the futex word. */ + ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG); + ASSERT_EQ(ret, 0) + TH_LOG("lock failed: errno=%d (%s)", errno, strerror(errno)); + ASSERT_EQ(pi_futex & FUTEX_TID_MASK, (futex_t)tid) + TH_LOG("owner TID not in futex word: 0x%08x", pi_futex); + + /* A recursive lock by the owner must be refused, not deadlock. */ + errno = 0; + ret = futex_lock_pi(&pi_futex, NULL, 0, FUTEX_PRIVATE_FLAG); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EDEADLK) + TH_LOG("recursive lock: expected EDEADLK, got errno=%d", errno); + + /* Release: the futex word is handed back clean. */ + ret = futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG); + ASSERT_EQ(ret, 0) + TH_LOG("unlock failed: errno=%d", errno); + ASSERT_EQ(pi_futex, (futex_t)0) + TH_LOG("futex word not cleared after unlock: 0x%08x", pi_futex); +} + +/* + * A PI waiter inherits the lock when the owner dies holding it. + * + * The owner parks while holding the lock, this thread contends for it, then + * the owner exits. The waiter must come out cleanly (no hang, no unexpected + * error) and, when it acquires, must actually own the lock. + */ +TEST_F(lock_pi_exiting, owner_dies_with_blocked_waiter) +{ + pthread_t owner, waiter; + + pthread_barrier_init(&locked_barrier, NULL, 2); + pthread_barrier_init(&release_barrier, NULL, 2); + pi_futex = FUTEX_INITIALIZER; + waiter_outcome = -1; + waiter_owns = 0; + + ASSERT_EQ(pthread_create(&owner, NULL, owner_thread, (void *)1), 0); + + /* Wait until the owner actually holds the lock. */ + pthread_barrier_wait(&locked_barrier); + + /* Start the waiter and give it time to block as a real PI waiter. */ + ASSERT_EQ(pthread_create(&waiter, NULL, waiter_thread, NULL), 0); + usleep(1000); + + /* Release the owner so it dies while the waiter is queued on it. */ + pthread_barrier_wait(&release_barrier); + + pthread_join(waiter, NULL); + pthread_join(owner, NULL); + + ASSERT_TRUE(outcome_ok(waiter_outcome)) { + TH_LOG("unexpected FUTEX_LOCK_PI outcome: %d (%s)", + waiter_outcome, strerror(waiter_outcome)); + } + if (waiter_outcome == 0 || waiter_outcome == EOWNERDEAD) { + ASSERT_TRUE(waiter_owns) + TH_LOG("waiter acquired but futex word lacks its TID"); + } + + pthread_barrier_destroy(&locked_barrier); + pthread_barrier_destroy(&release_barrier); +} + +/* + * Stress: repeatedly let an owner exit while a waiter contends for the lock. + * + * Each iteration drives the FUTEX_STATE_EXITING -> -EBUSY -> retry path that + * the stale-'exiting'-pointer bug lived on (210d36d892de). The warning it + * fixed is invisible to user space, so on a normally-configured kernel both + * the buggy and fixed kernels pass here; the point is to make a kernel booted + * with panic_on_warn=1 trip during one of these iterations. + */ +TEST_F(lock_pi_exiting, stress_owner_exits) +{ + for (int i = 0; i < STRESS_ITERS; i++) { + pthread_t owner; + int outcome; + + pthread_barrier_init(&locked_barrier, NULL, 2); + pi_futex = FUTEX_INITIALIZER; + + ASSERT_EQ(pthread_create(&owner, NULL, owner_thread, (void *)0), 0); + + /* Owner holds the lock; race FUTEX_LOCK_PI against its exit. */ + pthread_barrier_wait(&locked_barrier); + + outcome = waiter_lock_pi(); + ASSERT_TRUE(outcome_ok(outcome)) { + TH_LOG("iter %d: unexpected outcome %d (%s)", + i, outcome, strerror(outcome)); + } + if (outcome == 0 || outcome == EOWNERDEAD) + futex_unlock_pi(&pi_futex, FUTEX_PRIVATE_FLAG); + + pthread_join(owner, NULL); + pthread_barrier_destroy(&locked_barrier); + } +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_numa_mpol.c b/tools/testing/selftests/futex/functional/futex_numa_mpol.c index 78c0f7a59e17..4ffcf41efe1f 100644 --- a/tools/testing/selftests/futex/functional/futex_numa_mpol.c +++ b/tools/testing/selftests/futex/functional/futex_numa_mpol.c @@ -9,6 +9,7 @@ #include <pthread.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <unistd.h> #ifdef LIBNUMA_VER_SUFFICIENT #include <numa.h> @@ -28,9 +29,9 @@ static pthread_barrier_t barrier_main; static pthread_t threads[MAX_THREADS]; struct thread_args { - void *futex_ptr; - unsigned int flags; - int result; + void *futex_ptr; + unsigned int flags; + int result; }; static struct thread_args thread_args[MAX_THREADS]; @@ -54,7 +55,7 @@ static void *thread_lock_fn(void *arg) return NULL; } -static void create_max_threads(void *futex_ptr) +static void create_max_threads(struct __test_metadata *_metadata, void *futex_ptr) { int i, ret; @@ -63,28 +64,29 @@ static void create_max_threads(void *futex_ptr) thread_args[i].flags = FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA; thread_args[i].result = 0; ret = pthread_create(&threads[i], NULL, thread_lock_fn, &thread_args[i]); - if (ret) - ksft_exit_fail_msg("pthread_create failed\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed"); } } -static void join_max_threads(void) +static void join_max_threads(struct __test_metadata *_metadata) { int i, ret; for (i = 0; i < MAX_THREADS; i++) { ret = pthread_join(threads[i], NULL); - if (ret) - ksft_exit_fail_msg("pthread_join failed for thread %d\n", i); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_join failed for thread %d", i); } } -static void __test_futex(void *futex_ptr, int err_value, unsigned int futex_flags) +static void __test_futex(struct __test_metadata *_metadata, void *futex_ptr, int err_value, + unsigned int futex_flags) { - int to_wake, ret, i, need_exit = 0; + int to_wake, ret, i; pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); - create_max_threads(futex_ptr); + create_max_threads(_metadata, futex_ptr); pthread_barrier_wait(&barrier_main); to_wake = MAX_THREADS; @@ -92,45 +94,50 @@ static void __test_futex(void *futex_ptr, int err_value, unsigned int futex_flag ret = futex2_wake(futex_ptr, to_wake, futex_flags); if (err_value) { - if (ret >= 0) - ksft_exit_fail_msg("futex2_wake(%d, 0x%x) should fail, but didn't\n", - to_wake, futex_flags); + EXPECT_LT(ret, 0) { + TH_LOG("futex2_wake(%d, 0x%x) should fail, but didn't", + to_wake, futex_flags); + } - if (errno != err_value) - ksft_exit_fail_msg("futex2_wake(%d, 0x%x) expected error was %d, but returned %d (%s)\n", - to_wake, futex_flags, err_value, errno, strerror(errno)); + EXPECT_EQ(errno, err_value) { + TH_LOG("futex2_wake(%d, 0x%x) expected error was %d, but returned %d (%s)", + to_wake, futex_flags, err_value, errno, strerror(errno)); + } break; } if (ret < 0) { - ksft_exit_fail_msg("Failed futex2_wake(%d, 0x%x): %m\n", - to_wake, futex_flags); + if (errno == ENOSYS || (errno == EINVAL && (futex_flags & FUTEX2_NUMA))) + SKIP(return, "futex2 or FUTEX2_NUMA not supported by kernel"); + + ASSERT_GE(ret, 0) { + TH_LOG("Failed futex2_wake(%d, 0x%x): %s", + to_wake, futex_flags, strerror(errno)); + } } if (!ret) usleep(50); to_wake -= ret; } while (to_wake); - join_max_threads(); + join_max_threads(_metadata); for (i = 0; i < MAX_THREADS; i++) { - if (err_value && thread_args[i].result != -1) { - ksft_print_msg("Thread %d should fail but succeeded (%d)\n", + if (err_value) { + EXPECT_EQ(thread_args[i].result, -1) { + TH_LOG("Thread %d should fail but succeeded (%d)", i, thread_args[i].result); - need_exit = 1; - } - if (!err_value && thread_args[i].result != 0) { - ksft_print_msg("Thread %d failed (%d)\n", i, thread_args[i].result); - need_exit = 1; + } + } else { + EXPECT_EQ(thread_args[i].result, 0) + TH_LOG("Thread %d failed (%d)", i, thread_args[i].result); } } - if (need_exit) - ksft_exit_fail_msg("Aborting due to earlier errors.\n"); } -static void test_futex(void *futex_ptr, int err_value) +static void test_futex(struct __test_metadata *_metadata, void *futex_ptr, int err_value) { - __test_futex(futex_ptr, err_value, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA); + __test_futex(_metadata, futex_ptr, err_value, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA); } TEST(futex_numa_mpol) @@ -141,43 +148,41 @@ TEST(futex_numa_mpol) mem_size = sysconf(_SC_PAGE_SIZE); futex_ptr = mmap(NULL, mem_size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - if (futex_ptr == MAP_FAILED) - ksft_exit_fail_msg("mmap() for %d bytes failed\n", mem_size); + ASSERT_NE(futex_ptr, MAP_FAILED) + TH_LOG("mmap() for %d bytes failed: %s", mem_size, strerror(errno)); /* Create an invalid memory region for the "Memory out of range" test */ mprotect(futex_ptr + mem_size, mem_size, PROT_NONE); futex_numa = futex_ptr; - ksft_print_msg("Regular test\n"); + TH_LOG("Regular test"); futex_numa->futex = 0; futex_numa->numa = FUTEX_NO_NODE; - test_futex(futex_ptr, 0); + test_futex(_metadata, futex_ptr, 0); - if (futex_numa->numa == FUTEX_NO_NODE) - ksft_exit_fail_msg("NUMA node is left uninitialized\n"); + EXPECT_NE(futex_numa->numa, FUTEX_NO_NODE) + TH_LOG("NUMA node is left uninitialized"); /* FUTEX2_NUMA futex must be 8-byte aligned */ - ksft_print_msg("Mis-aligned futex\n"); - test_futex(futex_ptr + mem_size - 4, EINVAL); + TH_LOG("Mis-aligned futex"); + test_futex(_metadata, futex_ptr + mem_size - 4, EINVAL); - ksft_print_msg("Memory out of range\n"); - test_futex(futex_ptr + mem_size, EFAULT); + TH_LOG("Memory out of range"); + test_futex(_metadata, futex_ptr + mem_size, EFAULT); futex_numa->numa = FUTEX_NO_NODE; mprotect(futex_ptr, mem_size, PROT_READ); - ksft_print_msg("Memory, RO\n"); - test_futex(futex_ptr, EFAULT); + TH_LOG("Memory, RO"); + test_futex(_metadata, futex_ptr, EFAULT); mprotect(futex_ptr, mem_size, PROT_NONE); - ksft_print_msg("Memory, no access\n"); - test_futex(futex_ptr, EFAULT); + TH_LOG("Memory, no access"); + test_futex(_metadata, futex_ptr, EFAULT); mprotect(futex_ptr, mem_size, PROT_READ | PROT_WRITE); - ksft_print_msg("Memory back to RW\n"); - test_futex(futex_ptr, 0); - - ksft_test_result_pass("futex2 memory boundary tests passed\n"); + TH_LOG("Memory back to RW"); + test_futex(_metadata, futex_ptr, 0); /* MPOL test. Does not work as expected */ #ifdef LIBNUMA_VER_SUFFICIENT @@ -190,25 +195,23 @@ TEST(futex_numa_mpol) sizeof(nodemask) * 8, 0); if (ret == 0) { ret = numa_set_mempolicy_home_node(futex_ptr, mem_size, i, 0); - if (ret != 0) - ksft_exit_fail_msg("Failed to set home node: %m, %d\n", errno); + ASSERT_EQ(ret, 0) + TH_LOG("Failed to set home node: %s, %d", strerror(errno), errno); - ksft_print_msg("Node %d test\n", i); + TH_LOG("Node %d test", i); futex_numa->futex = 0; futex_numa->numa = FUTEX_NO_NODE; - ret = futex2_wake(futex_ptr, 0, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | FUTEX2_NUMA | FUTEX2_MPOL); - if (ret < 0) - ksft_test_result_fail("Failed to wake 0 with MPOL: %m\n"); - if (futex_numa->numa != i) { - ksft_exit_fail_msg("Returned NUMA node is %d expected %d\n", - futex_numa->numa, i); - } + ret = futex2_wake(futex_ptr, 0, FUTEX2_SIZE_U32 | FUTEX_PRIVATE_FLAG | + FUTEX2_NUMA | FUTEX2_MPOL); + EXPECT_GE(ret, 0) + TH_LOG("Failed to wake 0 with MPOL: %s", strerror(errno)); + EXPECT_EQ(futex_numa->numa, i) + TH_LOG("Returned NUMA node is %d expected %d", futex_numa->numa, i); } } - ksft_test_result_pass("futex2 MPOL hints test passed\n"); #else - ksft_test_result_skip("futex2 MPOL hints test requires libnuma 2.0.18+\n"); + SKIP(return, "futex2 MPOL hints test requires libnuma 2.0.18+"); #endif munmap(futex_ptr, mem_size * 2); } diff --git a/tools/testing/selftests/futex/functional/futex_priv_hash.c b/tools/testing/selftests/futex/functional/futex_priv_hash.c index e8079d7c65e8..a8742e204540 100644 --- a/tools/testing/selftests/futex/functional/futex_priv_hash.c +++ b/tools/testing/selftests/futex/functional/futex_priv_hash.c @@ -9,6 +9,7 @@ #include <pthread.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <unistd.h> #include <linux/prctl.h> @@ -39,31 +40,28 @@ static int futex_hash_slots_get(void) return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS); } -static void futex_hash_slots_set_verify(int slots) +static void futex_hash_slots_set_verify(struct __test_metadata *_metadata, int slots) { int ret; ret = futex_hash_slots_set(slots); - if (ret != 0) { - ksft_test_result_fail("Failed to set slots to %d: %m\n", slots); - ksft_finished(); - } + ASSERT_EQ(ret, 0) + TH_LOG("Failed to set slots to %d: %s", slots, strerror(errno)); + ret = futex_hash_slots_get(); - if (ret != slots) { - ksft_test_result_fail("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %m\n", - slots, ret); - ksft_finished(); + ASSERT_EQ(ret, slots) { + TH_LOG("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d, %s", + slots, ret, strerror(errno)); } - ksft_test_result_pass("SET and GET slots %d passed\n", slots); } -static void futex_hash_slots_set_must_fail(int slots) +static void futex_hash_slots_set_must_fail(struct __test_metadata *_metadata, int slots) { int ret; ret = futex_hash_slots_set(slots); - ksft_test_result(ret < 0, "futex_hash_slots_set(%d)\n", - slots); + EXPECT_LT(ret, 0) + TH_LOG("futex_hash_slots_set(%d) should fail but succeeded", slots); } static void *thread_return_fn(void *arg) @@ -82,32 +80,32 @@ static void *thread_lock_fn(void *arg) return NULL; } -static void create_max_threads(void *(*thread_fn)(void *)) +static void create_max_threads(struct __test_metadata *_metadata, void *(*thread_fn)(void *)) { int i, ret; for (i = 0; i < MAX_THREADS; i++) { ret = pthread_create(&threads[i], NULL, thread_fn, NULL); - if (ret) - ksft_exit_fail_msg("pthread_create failed: %m\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed: %s", strerror(errno)); } } -static void join_max_threads(void) +static void join_max_threads(struct __test_metadata *_metadata) { int i, ret; for (i = 0; i < MAX_THREADS; i++) { ret = pthread_join(threads[i], NULL); - if (ret) - ksft_exit_fail_msg("pthread_join failed for thread %d\n", i); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_join failed for thread %d: %s", i, strerror(errno)); } } #define SEC_IN_NSEC 1000000000 #define MSEC_IN_NSEC 1000000 -static void futex_dummy_op(void) +static void futex_dummy_op(struct __test_metadata *_metadata) { pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; struct timespec timeout; @@ -121,11 +119,11 @@ static void futex_dummy_op(void) timeout.tv_sec++; } ret = pthread_mutex_timedlock(&lock, &timeout); - if (ret == 0) - ksft_exit_fail_msg("Successfully locked an already locked mutex.\n"); + ASSERT_NE(ret, 0) + TH_LOG("Successfully locked an already locked mutex"); - if (ret != ETIMEDOUT) - ksft_exit_fail_msg("pthread_mutex_timedlock() did not timeout: %d.\n", ret); + ASSERT_EQ(ret, ETIMEDOUT) + TH_LOG("pthread_mutex_timedlock() did not timeout: %d", ret); } static const char *test_msg_auto_create = "Automatic hash bucket init on thread creation.\n"; @@ -140,50 +138,48 @@ TEST(priv_hash) ret = pthread_mutexattr_init(&mutex_attr_pi); ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT); ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi); - if (ret != 0) { - ksft_exit_fail_msg("Failed to initialize pthread mutex.\n"); - } + ASSERT_EQ(ret, 0) + TH_LOG("Failed to initialize pthread mutex"); + /* First thread, expect to be 0, not yet initialized */ ret = futex_hash_slots_get(); - if (ret != 0) - ksft_exit_fail_msg("futex_hash_slots_get() failed: %d, %m\n", ret); + if (ret < 0 && errno == EINVAL) + SKIP(return, "PR_FUTEX_HASH not supported by kernel"); + + ASSERT_EQ(ret, 0) + TH_LOG("futex_hash_slots_get() failed: %d, %s", ret, strerror(errno)); - ksft_test_result_pass("Basic get slots and immutable status.\n"); ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL); - if (ret != 0) - ksft_exit_fail_msg("pthread_create() failed: %d, %m\n", ret); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create() failed: %d, %s", ret, strerror(errno)); ret = pthread_join(threads[0], NULL); - if (ret != 0) - ksft_exit_fail_msg("pthread_join() failed: %d, %m\n", ret); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_join() failed: %d, %s", ret, strerror(errno)); /* First thread, has to initialize private hash */ futex_slots1 = futex_hash_slots_get(); - if (futex_slots1 <= 0) { - ksft_print_msg("Current hash buckets: %d\n", futex_slots1); - ksft_exit_fail_msg("%s", test_msg_auto_create); - } - - ksft_test_result_pass("%s", test_msg_auto_create); + EXPECT_GT(futex_slots1, 0) + TH_LOG("Current hash buckets: %d. %s", futex_slots1, test_msg_auto_create); online_cpus = sysconf(_SC_NPROCESSORS_ONLN); ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1); - if (ret != 0) - ksft_exit_fail_msg("pthread_barrier_init failed: %m.\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); ret = pthread_mutex_lock(&global_lock); - if (ret != 0) - ksft_exit_fail_msg("pthread_mutex_lock failed: %m.\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_mutex_lock failed: %s", strerror(errno)); counter = 0; - create_max_threads(thread_lock_fn); + create_max_threads(_metadata, thread_lock_fn); pthread_barrier_wait(&barrier_main); /* * The current default size of hash buckets is 16. The auto increase * works only if more than 16 CPUs are available. */ - ksft_print_msg("Online CPUs: %d\n", online_cpus); + TH_LOG("Online CPUs: %d", online_cpus); if (online_cpus > 16) { retry_getslots: futex_slotsn = futex_hash_slots_get(); @@ -200,71 +196,76 @@ retry_getslots: * sleep for 100ms and issue a futex operation. */ if (retry > 0) { - futex_dummy_op(); + futex_dummy_op(_metadata); goto retry_getslots; } - ksft_print_msg("Expected increase of hash buckets but got: %d -> %d\n", - futex_slots1, futex_slotsn); - ksft_exit_fail_msg("%s", test_msg_auto_inc); + EXPECT_NE(futex_slots1, futex_slotsn) { + TH_LOG("Expected increase of hash buckets but got: %d -> %d. %s", + futex_slots1, futex_slotsn, test_msg_auto_inc); + } } - ksft_test_result_pass("%s", test_msg_auto_inc); } else { - ksft_test_result_skip("%s", test_msg_auto_inc); + SKIP(return, "Automatic increase with more than 16 CPUs (only %d online)", online_cpus); } ret = pthread_mutex_unlock(&global_lock); /* Once the user changes it, it has to be what is set */ - futex_hash_slots_set_verify(2); - futex_hash_slots_set_verify(4); - futex_hash_slots_set_verify(8); - futex_hash_slots_set_verify(32); - futex_hash_slots_set_verify(16); + futex_hash_slots_set_verify(_metadata, 2); + futex_hash_slots_set_verify(_metadata, 4); + futex_hash_slots_set_verify(_metadata, 8); + futex_hash_slots_set_verify(_metadata, 32); + futex_hash_slots_set_verify(_metadata, 16); ret = futex_hash_slots_set(15); - ksft_test_result(ret < 0, "Use 15 slots\n"); + EXPECT_LT(ret, 0) + TH_LOG("Use 15 slots should fail but succeeded"); + + futex_hash_slots_set_verify(_metadata, 2); + join_max_threads(_metadata); + + EXPECT_EQ(counter, MAX_THREADS) + TH_LOG("Created and waited for %d of %d threads", counter, MAX_THREADS); - futex_hash_slots_set_verify(2); - join_max_threads(); - ksft_test_result(counter == MAX_THREADS, "Created and waited for %d of %d threads\n", - counter, MAX_THREADS); counter = 0; /* Once the user set something, auto resize must be disabled */ ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); - create_max_threads(thread_lock_fn); - join_max_threads(); + create_max_threads(_metadata, thread_lock_fn); + join_max_threads(_metadata); ret = futex_hash_slots_get(); - ksft_test_result(ret == 2, "No more auto-resize after manual setting, got %d\n", - ret); + EXPECT_EQ(ret, 2) + TH_LOG("No more auto-resize after manual setting, got %d", ret); - futex_hash_slots_set_must_fail(1 << 29); - futex_hash_slots_set_verify(4); + futex_hash_slots_set_must_fail(_metadata, 1 << 29); + futex_hash_slots_set_verify(_metadata, 4); /* * Once the global hash has been requested, then this requested can not * be undone. */ ret = futex_hash_slots_set(0); - ksft_test_result(ret == 0, "Global hash request\n"); - if (ret != 0) - return; + ASSERT_EQ(ret, 0) + TH_LOG("Global hash request failed: %s", strerror(errno)); - futex_hash_slots_set_must_fail(4); - futex_hash_slots_set_must_fail(8); - futex_hash_slots_set_must_fail(8); - futex_hash_slots_set_must_fail(0); - futex_hash_slots_set_must_fail(6); + futex_hash_slots_set_must_fail(_metadata, 4); + futex_hash_slots_set_must_fail(_metadata, 8); + futex_hash_slots_set_must_fail(_metadata, 8); + futex_hash_slots_set_must_fail(_metadata, 0); + futex_hash_slots_set_must_fail(_metadata, 6); ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS); - if (ret != 0) - ksft_exit_fail_msg("pthread_barrier_init failed: %m\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_barrier_init failed: %s", strerror(errno)); - create_max_threads(thread_lock_fn); - join_max_threads(); + create_max_threads(_metadata, thread_lock_fn); + join_max_threads(_metadata); ret = futex_hash_slots_get(); - ksft_test_result(ret == 0, "Continue to use global hash\n"); + EXPECT_EQ(ret, 0) + TH_LOG("Continue to use global hash failed"); } TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_requeue.c b/tools/testing/selftests/futex/functional/futex_requeue.c index dcf0d5f2f312..cc31f051765d 100644 --- a/tools/testing/selftests/futex/functional/futex_requeue.c +++ b/tools/testing/selftests/futex/functional/futex_requeue.c @@ -5,55 +5,70 @@ * futex cmp requeue test by André Almeida <andrealmeid@collabora.com> */ -#include <pthread.h> #include <limits.h> +#include <pthread.h> +#include <string.h> #include "futextest.h" +#include "futex_thread.h" #include "kselftest_harness.h" -#define timeout_ns 30000000 -#define WAKE_WAIT_US 10000 +struct waiter_args { + struct __test_metadata *_metadata; + unsigned int n_threads; +}; volatile futex_t *f1; -void *waiterfn(void *arg) +static int waiterfn(void *arg) { - struct timespec to; + struct __test_metadata *_metadata; + struct waiter_args *wargs = arg; + struct timespec to = { }; + int res; - to.tv_sec = 0; - to.tv_nsec = timeout_ns; + _metadata = wargs->_metadata; + to.tv_sec = (wargs->n_threads + 1) * WAIT_FOR_THREAD_SECS; - if (futex_wait(f1, *f1, &to, 0)) - printf("waiter failed errno %d\n", errno); + res = futex_wait(f1, *f1, &to, 0); + if (res) { + EXPECT_EQ(res, 0) + TH_LOG("waiter failed errno %d: %s", errno, strerror(errno)); + } - return NULL; + return 0; } TEST(requeue_single) { + struct waiter_args wargs = { ._metadata = _metadata, .n_threads = 1 }; + struct futex_thread waiter; volatile futex_t _f1 = 0; volatile futex_t f2 = 0; - pthread_t waiter[10]; f1 = &_f1; /* * Requeue a waiter from f1 to f2, and wake f2. */ - ASSERT_EQ(0, pthread_create(&waiter[0], NULL, waiterfn, NULL)); + ASSERT_EQ(futex_thread_create(&waiter, waiterfn, &wargs), 0) + TH_LOG("pthread_create failed"); - usleep(WAKE_WAIT_US); + ASSERT_EQ(futex_wait_for_thread(&waiter, _metadata), 0) + TH_LOG("Wait for thread failed"); - EXPECT_EQ(1, futex_cmp_requeue(f1, 0, &f2, 0, 1, 0)); - EXPECT_EQ(1, futex_wake(&f2, 1, 0)); + EXPECT_EQ(futex_cmp_requeue(f1, 0, &f2, 0, 1, 0), 1); + EXPECT_EQ(futex_wake(&f2, 1, 0), 1); + + EXPECT_EQ(futex_thread_destroy(&waiter), 0); } TEST(requeue_multiple) { + struct waiter_args wargs = { ._metadata = _metadata, .n_threads = 10 }; + struct futex_thread waiter[10]; volatile futex_t _f1 = 0; volatile futex_t f2 = 0; - pthread_t waiter[10]; - int i; f1 = &_f1; @@ -61,13 +76,21 @@ TEST(requeue_multiple) * Create 10 waiters at f1. At futex_requeue, wake 3 and requeue 7. * At futex_wake, wake INT_MAX (should be exactly 7). */ - for (i = 0; i < 10; i++) - ASSERT_EQ(0, pthread_create(&waiter[i], NULL, waiterfn, NULL)); + for (int i = 0; i < 10; i++) { + ASSERT_EQ(futex_thread_create(&waiter[i], waiterfn, &wargs), 0) + TH_LOG("pthread_create failed for waiter %d", i); + } + + for (int i = 0; i < 10; i++) { + ASSERT_EQ(futex_wait_for_thread(&waiter[i], _metadata), 0) + TH_LOG("Wait for waiter thread %d failed", i); + } - usleep(WAKE_WAIT_US); + EXPECT_EQ(futex_cmp_requeue(f1, 0, &f2, 3, 7, 0), 10); + EXPECT_EQ(futex_wake(&f2, INT_MAX, 0), 7); - EXPECT_EQ(10, futex_cmp_requeue(f1, 0, &f2, 3, 7, 0)); - EXPECT_EQ(7, futex_wake(&f2, INT_MAX, 0)); + for (int i = 0; i < 10; i++) + EXPECT_EQ(futex_thread_destroy(&waiter[i]), 0); } TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_requeue_pi.c b/tools/testing/selftests/futex/functional/futex_requeue_pi.c index 46d2858e15a8..65326cbef3a4 100644 --- a/tools/testing/selftests/futex/functional/futex_requeue_pi.c +++ b/tools/testing/selftests/futex/functional/futex_requeue_pi.c @@ -43,12 +43,13 @@ futex_t f2 = FUTEX_INITIALIZER; futex_t wake_complete = FUTEX_INITIALIZER; struct thread_arg { - long id; - struct timespec *timeout; - int lock; - int ret; + struct __test_metadata *_metadata; + long id; + struct timespec *timeout; + int lock; + int ret; }; -#define THREAD_ARG_INITIALIZER { 0, NULL, 0, 0 } +#define THREAD_ARG_INITIALIZER { NULL, 0, NULL, 0, 0 } FIXTURE(args) { @@ -118,7 +119,7 @@ FIXTURE_VARIANT_ADD_TIMEOUT(5000); FIXTURE_VARIANT_ADD_TIMEOUT(500000); FIXTURE_VARIANT_ADD_TIMEOUT(2000000000); -int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg, +int create_rt_thread(struct __test_metadata *_metadata, pthread_t *pth, void*(*func)(void *), void *arg, int policy, int prio) { int ret; @@ -129,29 +130,22 @@ int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg, memset(&schedp, 0, sizeof(schedp)); ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); - if (ret) { - ksft_exit_fail_msg("pthread_attr_setinheritsched\n"); - return -1; - } + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setinheritsched failed"); ret = pthread_attr_setschedpolicy(&attr, policy); - if (ret) { - ksft_exit_fail_msg("pthread_attr_setschedpolicy\n"); - return -1; - } + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setschedpolicy failed"); schedp.sched_priority = prio; ret = pthread_attr_setschedparam(&attr, &schedp); - if (ret) { - ksft_exit_fail_msg("pthread_attr_setschedparam\n"); - return -1; - } + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setschedparam failed"); ret = pthread_create(pth, &attr, func, arg); - if (ret) { - ksft_exit_fail_msg("pthread_create\n"); - return -1; - } + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed"); + return 0; } @@ -159,70 +153,76 @@ int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg, void *waiterfn(void *arg) { struct thread_arg *args = (struct thread_arg *)arg; + struct __test_metadata *_metadata = args->_metadata; futex_t old_val; - ksft_print_dbg_msg("Waiter %ld: running\n", args->id); + TH_LOG("Waiter %ld: running", args->id); /* Each thread sleeps for a different amount of time * This is to avoid races, because we don't lock the - * external mutex here */ + * external mutex here + */ usleep(1000 * (long)args->id); old_val = f1; atomic_inc(&waiters_blocked); - ksft_print_dbg_msg("Calling futex_wait_requeue_pi: %p (%u) -> %p\n", - &f1, f1, &f2); + TH_LOG("Calling futex_wait_requeue_pi: %p (%u) -> %p", &f1, f1, &f2); args->ret = futex_wait_requeue_pi(&f1, old_val, &f2, args->timeout, FUTEX_PRIVATE_FLAG); - ksft_print_dbg_msg("waiter %ld woke with %d %s\n", args->id, args->ret, - args->ret < 0 ? strerror(errno) : ""); + TH_LOG("waiter %ld woke with %d %s", args->id, args->ret, + args->ret < 0 ? strerror(errno) : ""); atomic_inc(&waiters_woken); if (args->ret < 0) { - if (args->timeout && errno == ETIMEDOUT) + if (args->timeout && errno == ETIMEDOUT) { args->ret = 0; - else { - ksft_exit_fail_msg("futex_wait_requeue_pi\n"); + } else { + ASSERT_EQ(args->ret, 0) + TH_LOG("futex_wait_requeue_pi failed: %s", strerror(errno)); } futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); } futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); - ksft_print_dbg_msg("Waiter %ld: exiting with %d\n", args->id, args->ret); + TH_LOG("Waiter %ld: exiting with %d", args->id, args->ret); pthread_exit((void *)&args->ret); } void *broadcast_wakerfn(void *arg) { struct thread_arg *args = (struct thread_arg *)arg; + struct __test_metadata *_metadata = args->_metadata; int nr_requeue = INT_MAX; int task_count = 0; futex_t old_val; int nr_wake = 1; int i = 0; - ksft_print_dbg_msg("Waker: waiting for waiters to block\n"); + TH_LOG("Waker: waiting for waiters to block"); while (waiters_blocked.val < THREAD_MAX) usleep(1000); usleep(1000); - ksft_print_dbg_msg("Waker: Calling broadcast\n"); + TH_LOG("Waker: Calling broadcast"); if (args->lock) { - ksft_print_dbg_msg("Calling FUTEX_LOCK_PI on mutex=%x @ %p\n", f2, &f2); + TH_LOG("Calling FUTEX_LOCK_PI on mutex=%x @ %p", f2, &f2); futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); } continue_requeue: old_val = f1; args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2, nr_wake, nr_requeue, - FUTEX_PRIVATE_FLAG); + FUTEX_PRIVATE_FLAG); if (args->ret < 0) { - ksft_exit_fail_msg("FUTEX_CMP_REQUEUE_PI failed\n"); + ASSERT_GE(args->ret, 0) + TH_LOG("FUTEX_CMP_REQUEUE_PI failed: %s", strerror(errno)); } else if (++i < MAX_WAKE_ITERS) { task_count += args->ret; if (task_count < THREAD_MAX - waiters_woken.val) goto continue_requeue; } else { - ksft_exit_fail_msg("max broadcast iterations (%d) reached with %d/%d tasks woken or requeued\n", - MAX_WAKE_ITERS, task_count, THREAD_MAX); + ASSERT_TRUE(0) { + TH_LOG("max broadcast iterations (%d) reached with %d/%d tasks woken or requeued", + MAX_WAKE_ITERS, task_count, THREAD_MAX); + } } futex_wake(&wake_complete, 1, FUTEX_PRIVATE_FLAG); @@ -233,33 +233,33 @@ void *broadcast_wakerfn(void *arg) if (args->ret > 0) args->ret = task_count; - ksft_print_dbg_msg("Waker: exiting with %d\n", args->ret); + TH_LOG("Waker: exiting with %d", args->ret); pthread_exit((void *)&args->ret); } void *signal_wakerfn(void *arg) { struct thread_arg *args = (struct thread_arg *)arg; + struct __test_metadata *_metadata = args->_metadata; unsigned int old_val; int nr_requeue = 0; int task_count = 0; int nr_wake = 1; int i = 0; - ksft_print_dbg_msg("Waker: waiting for waiters to block\n"); + TH_LOG("Waker: waiting for waiters to block"); while (waiters_blocked.val < THREAD_MAX) usleep(1000); usleep(1000); while (task_count < THREAD_MAX && waiters_woken.val < THREAD_MAX) { - ksft_print_dbg_msg("task_count: %d, waiters_woken: %d\n", + TH_LOG("task_count: %d, waiters_woken: %d", task_count, waiters_woken.val); if (args->lock) { - ksft_print_dbg_msg("Calling FUTEX_LOCK_PI on mutex=%x @ %p\n", - f2, &f2); + TH_LOG("Calling FUTEX_LOCK_PI on mutex=%x @ %p", f2, &f2); futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); } - ksft_print_dbg_msg("Waker: Calling signal\n"); + TH_LOG("Waker: Calling signal"); /* cond_signal */ old_val = f1; args->ret = futex_cmp_requeue_pi(&f1, old_val, &f2, @@ -267,23 +267,27 @@ void *signal_wakerfn(void *arg) FUTEX_PRIVATE_FLAG); if (args->ret < 0) args->ret = -errno; - ksft_print_dbg_msg("futex: %x\n", f2); + TH_LOG("futex: %x", f2); if (args->lock) { - ksft_print_dbg_msg("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p\n", + TH_LOG("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p", f2, &f2); futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); } - ksft_print_dbg_msg("futex: %x\n", f2); - if (args->ret < 0) - ksft_exit_fail_msg("FUTEX_CMP_REQUEUE_PI failed\n"); + TH_LOG("futex: %x", f2); + if (args->ret < 0) { + ASSERT_GE(args->ret, 0) + TH_LOG("FUTEX_CMP_REQUEUE_PI failed: %s", strerror(-args->ret)); + } task_count += args->ret; usleep(SIGNAL_PERIOD_US); i++; /* we have to loop at least THREAD_MAX times */ if (i > MAX_WAKE_ITERS + THREAD_MAX) { - ksft_exit_fail_msg("max signaling iterations (%d) reached, giving up on pending waiters.\n", - MAX_WAKE_ITERS + THREAD_MAX); + ASSERT_TRUE(0) { + TH_LOG("max signaling iterations (%d) reached, giving up on pending waiters.", + MAX_WAKE_ITERS + THREAD_MAX); + } } } @@ -292,14 +296,15 @@ void *signal_wakerfn(void *arg) if (args->ret >= 0) args->ret = task_count; - ksft_print_dbg_msg("Waker: exiting with %d\n", args->ret); - ksft_print_dbg_msg("Waker: waiters_woken: %d\n", waiters_woken.val); + TH_LOG("Waker: exiting with %d", args->ret); + TH_LOG("Waker: waiters_woken: %d", waiters_woken.val); pthread_exit((void *)&args->ret); } void *third_party_blocker(void *arg) { struct thread_arg *args = (struct thread_arg *)arg; + struct __test_metadata *_metadata = args->_metadata; int ret2 = 0; args->ret = futex_lock_pi(&f2, NULL, 0, FUTEX_PRIVATE_FLAG); @@ -310,8 +315,10 @@ void *third_party_blocker(void *arg) ret2 = futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); out: - if (args->ret || ret2) - ksft_exit_fail_msg("third_party_blocker() futex error"); + if (args->ret || ret2) { + ASSERT_TRUE(0) + TH_LOG("%s() futex error", __func__); + } pthread_exit((void *)&args->ret); } @@ -330,20 +337,19 @@ TEST_F(args, futex_requeue_pi) bool lock = variant->locked; int *waiter_ret, i, ret = 0; - ksft_print_msg( - "\tArguments: broadcast=%d locked=%d owner=%d timeout=%ldns\n", + TH_LOG("Arguments: broadcast=%d locked=%d owner=%d timeout=%ldns", broadcast, lock, third_party_owner, timeout_ns); if (timeout_ns) { time_t secs; - ksft_print_dbg_msg("timeout_ns = %ld\n", timeout_ns); + TH_LOG("timeout_ns = %ld", timeout_ns); ret = clock_gettime(CLOCK_MONOTONIC, &ts); secs = (ts.tv_nsec + timeout_ns) / 1000000000; ts.tv_nsec = ((int64_t)ts.tv_nsec + timeout_ns) % 1000000000; ts.tv_sec += secs; - ksft_print_dbg_msg("ts.tv_sec = %ld\n", ts.tv_sec); - ksft_print_dbg_msg("ts.tv_nsec = %ld\n", ts.tv_nsec); + TH_LOG("ts.tv_sec = %ld", ts.tv_sec); + TH_LOG("ts.tv_nsec = %ld", ts.tv_nsec); tsp = &ts; } @@ -351,34 +357,29 @@ TEST_F(args, futex_requeue_pi) wakerfn = broadcast_wakerfn; if (third_party_owner) { - if (create_rt_thread(&blocker, third_party_blocker, - (void *)&blocker_arg, SCHED_FIFO, 1)) { - ksft_exit_fail_msg("Creating third party blocker thread failed\n"); - } + blocker_arg._metadata = _metadata; + create_rt_thread(_metadata, &blocker, third_party_blocker, + (void *)&blocker_arg, SCHED_FIFO, 1); } atomic_set(&waiters_woken, 0); for (i = 0; i < THREAD_MAX; i++) { + args[i]._metadata = _metadata; args[i].id = i; args[i].timeout = tsp; - ksft_print_dbg_msg("Starting thread %d\n", i); - if (create_rt_thread(&waiter[i], waiterfn, (void *)&args[i], - SCHED_FIFO, 1)) { - ksft_exit_fail_msg("Creating waiting thread failed\n"); - } + TH_LOG("Starting thread %d", i); + create_rt_thread(_metadata, &waiter[i], waiterfn, (void *)&args[i], + SCHED_FIFO, 1); } + waker_arg._metadata = _metadata; waker_arg.lock = lock; - if (create_rt_thread(&waker, wakerfn, (void *)&waker_arg, - SCHED_FIFO, 1)) { - ksft_exit_fail_msg("Creating waker thread failed\n"); - } + create_rt_thread(_metadata, &waker, wakerfn, (void *)&waker_arg, SCHED_FIFO, 1); /* Wait for threads to finish */ /* Store the first error or failure encountered in waiter_ret */ waiter_ret = &args[0].ret; for (i = 0; i < THREAD_MAX; i++) - pthread_join(waiter[i], - *waiter_ret ? NULL : (void **)&waiter_ret); + pthread_join(waiter[i], *waiter_ret ? NULL : (void **)&waiter_ret); if (third_party_owner) pthread_join(blocker, NULL); @@ -393,8 +394,8 @@ TEST_F(args, futex_requeue_pi) ret = blocker_arg.ret; } - if (ret) - ksft_test_result_fail("fail"); + EXPECT_EQ(ret, 0) + TH_LOG("Test failed with error code: %d", ret); } TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_requeue_pi_mismatched_ops.c b/tools/testing/selftests/futex/functional/futex_requeue_pi_mismatched_ops.c index f686e605359c..35bb8a807bb9 100644 --- a/tools/testing/selftests/futex/functional/futex_requeue_pi_mismatched_ops.c +++ b/tools/testing/selftests/futex/functional/futex_requeue_pi_mismatched_ops.c @@ -29,14 +29,17 @@ futex_t f1 = FUTEX_INITIALIZER; futex_t f2 = FUTEX_INITIALIZER; -int child_ret = 0; +int child_ret; void *blocking_child(void *arg) { + struct __test_metadata *_metadata = (struct __test_metadata *)arg; + child_ret = futex_wait(&f1, f1, NULL, FUTEX_PRIVATE_FLAG); if (child_ret < 0) { child_ret = -errno; - ksft_exit_fail_msg("futex_wait\n"); + ASSERT_EQ(child_ret, 0) + TH_LOG("futex_wait failed: %s", strerror(errno)); } return (void *)&child_ret; } @@ -46,8 +49,8 @@ TEST(requeue_pi_mismatched_ops) pthread_t child; int ret; - if (pthread_create(&child, NULL, blocking_child, NULL)) - ksft_exit_fail_msg("pthread_create\n"); + ASSERT_EQ(pthread_create(&child, NULL, blocking_child, _metadata), 0) + TH_LOG("pthread_create failed"); /* Allow the child to block in the kernel. */ sleep(1); @@ -67,27 +70,33 @@ TEST(requeue_pi_mismatched_ops) * FUTEX_WAKE. */ ret = futex_wake(&f1, 1, FUTEX_PRIVATE_FLAG); - if (ret == 1) + if (ret == 1) { ret = 0; - else if (ret < 0) - ksft_exit_fail_msg("futex_wake\n"); - else - ksft_exit_fail_msg("futex_wake did not wake the child\n"); + } else if (ret < 0) { + ASSERT_GE(ret, 0) + TH_LOG("futex_wake failed: %s", strerror(errno)); + } else { + ASSERT_TRUE(0) + TH_LOG("futex_wake did not wake the child"); + } } else { - ksft_exit_fail_msg("futex_cmp_requeue_pi\n"); + ASSERT_TRUE(0) + TH_LOG("futex_cmp_requeue_pi failed with unexpected errno: %s", strerror(errno)); } } else if (ret > 0) { - ksft_test_result_fail("futex_cmp_requeue_pi failed to detect the mismatch\n"); + EXPECT_EQ(ret, 0) + TH_LOG("futex_cmp_requeue_pi failed to detect the mismatch"); } else { - ksft_exit_fail_msg("futex_cmp_requeue_pi found no waiters\n"); + ASSERT_TRUE(0) + TH_LOG("futex_cmp_requeue_pi found no waiters"); } pthread_join(child, NULL); - if (!ret && !child_ret) - ksft_test_result_pass("futex_requeue_pi_mismatched_ops passed\n"); - else - ksft_test_result_pass("futex_requeue_pi_mismatched_ops failed\n"); + EXPECT_EQ(ret, 0) + TH_LOG("Test failed: ret=%d", ret); + EXPECT_EQ(child_ret, 0) + TH_LOG("Child failed: child_ret=%d", child_ret); } TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_requeue_pi_signal_restart.c b/tools/testing/selftests/futex/functional/futex_requeue_pi_signal_restart.c index a18ccae73eb1..4933612a7b55 100644 --- a/tools/testing/selftests/futex/functional/futex_requeue_pi_signal_restart.c +++ b/tools/testing/selftests/futex/functional/futex_requeue_pi_signal_restart.c @@ -35,10 +35,10 @@ futex_t f1 = FUTEX_INITIALIZER; futex_t f2 = FUTEX_INITIALIZER; atomic_t requeued = ATOMIC_INITIALIZER; -int waiter_ret = 0; +int waiter_ret; -int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg, - int policy, int prio) +int create_rt_thread(struct __test_metadata *_metadata, pthread_t *pth, void*(*func)(void *), + void *arg, int policy, int prio) { struct sched_param schedp; pthread_attr_t attr; @@ -48,45 +48,47 @@ int create_rt_thread(pthread_t *pth, void*(*func)(void *), void *arg, memset(&schedp, 0, sizeof(schedp)); ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); - if (ret) - ksft_exit_fail_msg("pthread_attr_setinheritsched\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setinheritsched failed"); ret = pthread_attr_setschedpolicy(&attr, policy); - if (ret) - ksft_exit_fail_msg("pthread_attr_setschedpolicy\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setschedpolicy failed"); schedp.sched_priority = prio; ret = pthread_attr_setschedparam(&attr, &schedp); - if (ret) - ksft_exit_fail_msg("pthread_attr_setschedparam\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_attr_setschedparam failed"); ret = pthread_create(pth, &attr, func, arg); - if (ret) - ksft_exit_fail_msg("pthread_create\n"); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed"); return 0; } void handle_signal(int signo) { - ksft_print_dbg_msg("signal received %s requeue\n", - requeued.val ? "after" : "prior to"); + printf("INFO: signal received %s requeue\n", requeued.val ? "after" : "prior to"); } void *waiterfn(void *arg) { + struct __test_metadata *_metadata = (struct __test_metadata *)arg; unsigned int old_val; int res; - ksft_print_dbg_msg("Waiter running\n"); - ksft_print_dbg_msg("Calling FUTEX_LOCK_PI on f2=%x @ %p\n", f2, &f2); + TH_LOG("Waiter running"); + TH_LOG("Calling FUTEX_LOCK_PI on f2=%x @ %p", f2, &f2); old_val = f1; res = futex_wait_requeue_pi(&f1, old_val, &(f2), NULL, FUTEX_PRIVATE_FLAG); if (!requeued.val || errno != EWOULDBLOCK) { - ksft_test_result_fail("unexpected return from futex_wait_requeue_pi: %d (%s)\n", - res, strerror(errno)); - ksft_print_dbg_msg("w2:futex: %x\n", f2); + EXPECT_TRUE(0) { + TH_LOG("unexpected return from futex_wait_requeue_pi: %d (%s)", + res, strerror(errno)); + } + TH_LOG("w2:futex: %x", f2); if (!res) futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); } @@ -94,7 +96,6 @@ void *waiterfn(void *arg) pthread_exit(NULL); } - TEST(futex_requeue_pi_signal_restart) { unsigned int old_val; @@ -105,19 +106,17 @@ TEST(futex_requeue_pi_signal_restart) sa.sa_handler = handle_signal; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; - if (sigaction(SIGUSR1, &sa, NULL)) - ksft_exit_fail_msg("sigaction\n"); + ASSERT_EQ(sigaction(SIGUSR1, &sa, NULL), 0) + TH_LOG("sigaction failed"); - ksft_print_dbg_msg("m1:f2: %x\n", f2); - ksft_print_dbg_msg("Creating waiter\n"); - res = create_rt_thread(&waiter, waiterfn, NULL, SCHED_FIFO, 1); - if (res) - ksft_exit_fail_msg("Creating waiting thread failed"); + TH_LOG("m1:f2: %x", f2); + TH_LOG("Creating waiter"); + create_rt_thread(_metadata, &waiter, waiterfn, _metadata, SCHED_FIFO, 1); - ksft_print_dbg_msg("Calling FUTEX_LOCK_PI on f2=%x @ %p\n", f2, &f2); - ksft_print_dbg_msg("m2:f2: %x\n", f2); + TH_LOG("Calling FUTEX_LOCK_PI on f2=%x @ %p", f2, &f2); + TH_LOG("m2:f2: %x", f2); futex_lock_pi(&f2, 0, 0, FUTEX_PRIVATE_FLAG); - ksft_print_dbg_msg("m3:f2: %x\n", f2); + TH_LOG("m3:f2: %x", f2); while (1) { /* @@ -125,11 +124,11 @@ TEST(futex_requeue_pi_signal_restart) * restart futex_wait_requeue_pi() in the kernel. Wait for the * waiter to block on f1 again. */ - ksft_print_dbg_msg("Issuing SIGUSR1 to waiter\n"); + TH_LOG("Issuing SIGUSR1 to waiter"); pthread_kill(waiter, SIGUSR1); usleep(DELAY_US); - ksft_print_dbg_msg("Requeueing waiter via FUTEX_CMP_REQUEUE_PI\n"); + TH_LOG("Requeueing waiter via FUTEX_CMP_REQUEUE_PI"); old_val = f1; res = futex_cmp_requeue_pi(&f1, old_val, &(f2), 1, 0, FUTEX_PRIVATE_FLAG); @@ -143,10 +142,11 @@ TEST(futex_requeue_pi_signal_restart) atomic_set(&requeued, 1); break; } else if (res < 0) { - ksft_exit_fail_msg("FUTEX_CMP_REQUEUE_PI failed\n"); + ASSERT_GE(res, 0) + TH_LOG("FUTEX_CMP_REQUEUE_PI failed: %s", strerror(errno)); } } - ksft_print_dbg_msg("m4:f2: %x\n", f2); + TH_LOG("m4:f2: %x", f2); /* * Signal the waiter after requeue, waiter should return from @@ -154,14 +154,14 @@ TEST(futex_requeue_pi_signal_restart) * futex_unlock_pi() can't happen before the signal wakeup is detected * in the kernel. */ - ksft_print_dbg_msg("Issuing SIGUSR1 to waiter\n"); + TH_LOG("Issuing SIGUSR1 to waiter"); pthread_kill(waiter, SIGUSR1); - ksft_print_dbg_msg("Waiting for waiter to return\n"); + TH_LOG("Waiting for waiter to return"); pthread_join(waiter, NULL); - ksft_print_dbg_msg("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p\n", f2, &f2); + TH_LOG("Calling FUTEX_UNLOCK_PI on mutex=%x @ %p", f2, &f2); futex_unlock_pi(&f2, FUTEX_PRIVATE_FLAG); - ksft_print_dbg_msg("m5:f2: %x\n", f2); + TH_LOG("m5:f2: %x", f2); } TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait.c b/tools/testing/selftests/futex/functional/futex_wait.c index 7b8879409007..5292f5d2c3b5 100644 --- a/tools/testing/selftests/futex/functional/futex_wait.c +++ b/tools/testing/selftests/futex/functional/futex_wait.c @@ -5,10 +5,11 @@ * futex cmp requeue test by André Almeida <andrealmeid@collabora.com> */ +#include <fcntl.h> #include <pthread.h> +#include <stdlib.h> #include <sys/shm.h> #include <sys/mman.h> -#include <fcntl.h> #include "futextest.h" #include "kselftest_harness.h" @@ -19,125 +20,157 @@ void *futex; +struct waiter_args { + struct __test_metadata *_metadata; + unsigned int flags; +}; + static void *waiterfn(void *arg) { + struct waiter_args *args = (struct waiter_args *)arg; + struct __test_metadata *_metadata = args->_metadata; struct timespec to; - unsigned int flags = 0; - - if (arg) - flags = *((unsigned int *) arg); + int res; to.tv_sec = 0; to.tv_nsec = timeout_ns; - if (futex_wait(futex, 0, &to, flags)) - printf("waiter failed errno %d\n", errno); + res = futex_wait(futex, 0, &to, args->flags); + if (res) { + EXPECT_EQ(res, 0) + TH_LOG("waiter failed errno %d: %s", errno, strerror(errno)); + } + free(args); return NULL; } TEST(private_futex) { - unsigned int flags = FUTEX_PRIVATE_FLAG; + struct waiter_args *args = malloc(sizeof(*args)); u_int32_t f_private = 0; pthread_t waiter; int res; + args->_metadata = _metadata; + args->flags = FUTEX_PRIVATE_FLAG; futex = &f_private; /* Testing a private futex */ - ksft_print_dbg_msg("Calling private futex_wait on futex: %p\n", futex); - if (pthread_create(&waiter, NULL, waiterfn, (void *) &flags)) - ksft_exit_fail_msg("pthread_create failed\n"); + TH_LOG("Calling private futex_wait on futex: %p", futex); + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0) + TH_LOG("pthread_create failed"); usleep(WAKE_WAIT_US); - ksft_print_dbg_msg("Calling private futex_wake on futex: %p\n", futex); + TH_LOG("Calling private futex_wake on futex: %p", futex); res = futex_wake(futex, 1, FUTEX_PRIVATE_FLAG); - if (res != 1) { - ksft_test_result_fail("futex_wake private returned: %d %s\n", - errno, strerror(errno)); - } else { - ksft_test_result_pass("futex_wake private succeeds\n"); - } + EXPECT_EQ(res, 1) + TH_LOG("futex_wake private returned: %d %s", res, res < 0 ? strerror(errno) : ""); + + pthread_join(waiter, NULL); } TEST(anon_page) { + struct waiter_args *args = malloc(sizeof(*args)); u_int32_t *shared_data; pthread_t waiter; int res, shm_id; + args->_metadata = _metadata; + args->flags = 0; + /* Testing an anon page shared memory */ shm_id = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666); if (shm_id < 0) { - if (errno == ENOSYS) - ksft_exit_skip("shmget syscall not supported\n"); - perror("shmget"); - exit(1); + if (errno == ENOSYS) { + free(args); + SKIP(return, "shmget syscall not supported"); + } + ASSERT_GE(shm_id, 0) + TH_LOG("shmget failed: %s", strerror(errno)); } shared_data = shmat(shm_id, NULL, 0); + if (shared_data == (void *)-1) { + free(args); + ASSERT_NE(shared_data, (void *)-1) + TH_LOG("shmat failed: %s", strerror(errno)); + } *shared_data = 0; futex = shared_data; - ksft_print_dbg_msg("Calling shared (page anon) futex_wait on futex: %p\n", futex); - if (pthread_create(&waiter, NULL, waiterfn, NULL)) - ksft_exit_fail_msg("pthread_create failed\n"); + TH_LOG("Calling shared (page anon) futex_wait on futex: %p", futex); + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0) + TH_LOG("pthread_create failed"); usleep(WAKE_WAIT_US); - ksft_print_dbg_msg("Calling shared (page anon) futex_wake on futex: %p\n", futex); + TH_LOG("Calling shared (page anon) futex_wake on futex: %p", futex); res = futex_wake(futex, 1, 0); - if (res != 1) { - ksft_test_result_fail("futex_wake shared (page anon) returned: %d %s\n", - errno, strerror(errno)); - } else { - ksft_test_result_pass("futex_wake shared (page anon) succeeds\n"); + EXPECT_EQ(res, 1) { + TH_LOG("futex_wake shared (page anon) returned: %d %s", + res, res < 0 ? strerror(errno) : ""); } + pthread_join(waiter, NULL); shmdt(shared_data); } TEST(file_backed) { + struct waiter_args *args = malloc(sizeof(*args)); u_int32_t f_private = 0; pthread_t waiter; int res, fd; void *shm; + args->_metadata = _metadata; + args->flags = 0; + /* Testing a file backed shared memory */ - fd = open(SHM_PATH, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); - if (fd < 0) - ksft_exit_fail_msg("open\n"); + fd = open(SHM_PATH, O_RDWR | O_CREAT, 0600); + if (fd < 0) { + free(args); + ASSERT_GE(fd, 0) + TH_LOG("open failed: %s", strerror(errno)); + } - if (ftruncate(fd, sizeof(f_private))) - ksft_exit_fail_msg("ftruncate\n"); + if (ftruncate(fd, sizeof(f_private))) { + free(args); + close(fd); + ASSERT_TRUE(0) + TH_LOG("ftruncate failed: %s", strerror(errno)); + } shm = mmap(NULL, sizeof(f_private), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (shm == MAP_FAILED) - ksft_exit_fail_msg("mmap\n"); + if (shm == MAP_FAILED) { + free(args); + close(fd); + ASSERT_NE(shm, MAP_FAILED) + TH_LOG("mmap failed: %s", strerror(errno)); + } memcpy(shm, &f_private, sizeof(f_private)); futex = shm; - ksft_print_dbg_msg("Calling shared (file backed) futex_wait on futex: %p\n", futex); - if (pthread_create(&waiter, NULL, waiterfn, NULL)) - ksft_exit_fail_msg("pthread_create failed\n"); + TH_LOG("Calling shared (file backed) futex_wait on futex: %p", futex); + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0) + TH_LOG("pthread_create failed"); usleep(WAKE_WAIT_US); - ksft_print_dbg_msg("Calling shared (file backed) futex_wake on futex: %p\n", futex); + TH_LOG("Calling shared (file backed) futex_wake on futex: %p", futex); res = futex_wake(shm, 1, 0); - if (res != 1) { - ksft_test_result_fail("futex_wake shared (file backed) returned: %d %s\n", - errno, strerror(errno)); - } else { - ksft_test_result_pass("futex_wake shared (file backed) succeeds\n"); + EXPECT_EQ(res, 1) { + TH_LOG("futex_wake shared (file backed) returned: %d %s", + res, res < 0 ? strerror(errno) : ""); } + pthread_join(waiter, NULL); munmap(shm, sizeof(f_private)); remove(SHM_PATH); close(fd); diff --git a/tools/testing/selftests/futex/functional/futex_wait_private_mapped_file.c b/tools/testing/selftests/futex/functional/futex_wait_private_mapped_file.c index 2a749f9b14eb..a9f7a02e3a0b 100644 --- a/tools/testing/selftests/futex/functional/futex_wait_private_mapped_file.c +++ b/tools/testing/selftests/futex/functional/futex_wait_private_mapped_file.c @@ -26,6 +26,7 @@ #include <pthread.h> #include <libgen.h> #include <signal.h> +#include <string.h> #include "futextest.h" #include "kselftest_harness.h" @@ -41,17 +42,22 @@ struct timespec wait_timeout = { .tv_sec = 5, .tv_nsec = 0}; void *thr_futex_wait(void *arg) { + struct __test_metadata *_metadata = (struct __test_metadata *)arg; int ret; - ksft_print_dbg_msg("futex wait\n"); + TH_LOG("futex wait"); ret = futex_wait(&val, 1, &wait_timeout, 0); - if (ret && errno != EWOULDBLOCK && errno != ETIMEDOUT) - ksft_exit_fail_msg("futex error.\n"); + if (ret && errno != EWOULDBLOCK && errno != ETIMEDOUT) { + ASSERT_TRUE(0) + TH_LOG("futex error: %s", strerror(errno)); + } - if (ret && errno == ETIMEDOUT) - ksft_exit_fail_msg("waiter timedout\n"); + if (ret && errno == ETIMEDOUT) { + ASSERT_TRUE(0) + TH_LOG("waiter timedout"); + } - ksft_print_dbg_msg("futex_wait: ret = %d, errno = %d\n", ret, errno); + TH_LOG("futex_wait: ret = %d, errno = %d", ret, errno); return NULL; } @@ -61,22 +67,20 @@ TEST(wait_private_mapped_file) pthread_t thr; int res; - res = pthread_create(&thr, NULL, thr_futex_wait, NULL); - if (res < 0) - ksft_exit_fail_msg("pthread_create error\n"); + res = pthread_create(&thr, NULL, thr_futex_wait, _metadata); + ASSERT_EQ(res, 0) + TH_LOG("pthread_create error"); - ksft_print_dbg_msg("wait a while\n"); + TH_LOG("wait a while"); usleep(WAKE_WAIT_US); val = 2; res = futex_wake(&val, 1, 0); - ksft_print_dbg_msg("futex_wake %d\n", res); - if (res != 1) - ksft_exit_fail_msg("FUTEX_WAKE didn't find the waiting thread.\n"); + TH_LOG("futex_wake %d", res); + EXPECT_EQ(res, 1) + TH_LOG("FUTEX_WAKE didn't find the waiting thread"); - ksft_print_dbg_msg("join\n"); + TH_LOG("join"); pthread_join(thr, NULL); - - ksft_test_result_pass("wait_private_mapped_file"); } TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait_timeout.c b/tools/testing/selftests/futex/functional/futex_wait_timeout.c index 674dd13af421..6e6e770fe96a 100644 --- a/tools/testing/selftests/futex/functional/futex_wait_timeout.c +++ b/tools/testing/selftests/futex/functional/futex_wait_timeout.c @@ -31,53 +31,47 @@ static pthread_barrier_t barrier; */ void *get_pi_lock(void *arg) { + struct __test_metadata *_metadata = (struct __test_metadata *)arg; int ret; volatile futex_t lock = 0; ret = futex_lock_pi(&futex_pi, NULL, 0, 0); - if (ret != 0) - ksft_exit_fail_msg("futex_lock_pi failed\n"); + ASSERT_EQ(ret, 0) + TH_LOG("futex_lock_pi failed"); pthread_barrier_wait(&barrier); /* Blocks forever */ ret = futex_wait(&lock, 0, NULL, 0); - ksft_exit_fail_msg("futex_wait failed\n"); + ASSERT_TRUE(0) + TH_LOG("futex_wait returned unexpectedly: %d", ret); return NULL; } -/* - * Check if the function returned the expected error - */ -static void test_timeout(int res, char *test_name, int err) -{ - if (!res || errno != err) { - ksft_test_result_fail("%s returned %d\n", test_name, - res < 0 ? errno : res); - } else { - ksft_test_result_pass("%s succeeds\n", test_name); - } -} - -/* - * Calculate absolute timeout and correct overflow - */ -static int futex_get_abs_timeout(clockid_t clockid, struct timespec *to, - long timeout_ns) -{ - if (clock_gettime(clockid, to)) - ksft_exit_fail_msg("clock_gettime failed\n"); - - to->tv_nsec += timeout_ns; - - if (to->tv_nsec >= 1000000000) { - to->tv_sec++; - to->tv_nsec -= 1000000000; - } - - return 0; -} +#define TEST_TIMEOUT(_res, _test_name, _err) do { \ + if ((_res) < 0 && errno == ENOSYS && (_err) != ENOSYS) { \ + SKIP(return, "%s is not supported (ENOSYS)", _test_name); \ + } \ + EXPECT_EQ((_res), -1) \ + TH_LOG("%s returned unexpected result: %d", _test_name, (_res));\ + if ((_res) == -1) { \ + EXPECT_EQ(errno, (_err)) { \ + TH_LOG("%s returned unexpected errno: %d (expected %d)",\ + _test_name, errno, (_err)); \ + } \ + } \ +} while (0) + +#define GET_ABS_TIMEOUT(_clockid, _to, _timeout_ns) do { \ + ASSERT_EQ(clock_gettime((_clockid), (_to)), 0) \ + TH_LOG("clock_gettime failed"); \ + (_to)->tv_nsec += (_timeout_ns); \ + if ((_to)->tv_nsec >= 1000000000) { \ + (_to)->tv_sec++; \ + (_to)->tv_nsec -= 1000000000; \ + } \ +} while (0) TEST(wait_bitset) { @@ -90,19 +84,17 @@ TEST(wait_bitset) to.tv_nsec = timeout_ns; res = futex_wait(&f1, f1, &to, 0); - test_timeout(res, "futex_wait relative", ETIMEDOUT); + TEST_TIMEOUT(res, "futex_wait relative", ETIMEDOUT); /* FUTEX_WAIT_BITSET with CLOCK_REALTIME */ - if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns)) - ksft_test_result_error("get_time error"); + GET_ABS_TIMEOUT(CLOCK_REALTIME, &to, timeout_ns); res = futex_wait_bitset(&f1, f1, &to, 1, FUTEX_CLOCK_REALTIME); - test_timeout(res, "futex_wait_bitset realtime", ETIMEDOUT); + TEST_TIMEOUT(res, "futex_wait_bitset realtime", ETIMEDOUT); /* FUTEX_WAIT_BITSET with CLOCK_MONOTONIC */ - if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns)) - ksft_test_result_error("get_time error"); + GET_ABS_TIMEOUT(CLOCK_MONOTONIC, &to, timeout_ns); res = futex_wait_bitset(&f1, f1, &to, 1, 0); - test_timeout(res, "futex_wait_bitset monotonic", ETIMEDOUT); + TEST_TIMEOUT(res, "futex_wait_bitset monotonic", ETIMEDOUT); } TEST(requeue_pi) @@ -112,17 +104,14 @@ TEST(requeue_pi) int res; /* FUTEX_WAIT_REQUEUE_PI with CLOCK_REALTIME */ - if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns)) - ksft_test_result_error("get_time error"); + GET_ABS_TIMEOUT(CLOCK_REALTIME, &to, timeout_ns); res = futex_wait_requeue_pi(&f1, f1, &futex_pi, &to, FUTEX_CLOCK_REALTIME); - test_timeout(res, "futex_wait_requeue_pi realtime", ETIMEDOUT); + TEST_TIMEOUT(res, "futex_wait_requeue_pi realtime", ETIMEDOUT); /* FUTEX_WAIT_REQUEUE_PI with CLOCK_MONOTONIC */ - if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns)) - ksft_test_result_error("get_time error"); + GET_ABS_TIMEOUT(CLOCK_MONOTONIC, &to, timeout_ns); res = futex_wait_requeue_pi(&f1, f1, &futex_pi, &to, 0); - test_timeout(res, "futex_wait_requeue_pi monotonic", ETIMEDOUT); - + TEST_TIMEOUT(res, "futex_wait_requeue_pi monotonic", ETIMEDOUT); } TEST(lock_pi) @@ -133,7 +122,8 @@ TEST(lock_pi) /* Create a thread that will lock forever so any waiter will timeout */ pthread_barrier_init(&barrier, NULL, 2); - pthread_create(&thread, NULL, get_pi_lock, NULL); + ASSERT_EQ(pthread_create(&thread, NULL, get_pi_lock, _metadata), 0) + TH_LOG("pthread_create failed"); /* Wait until the other thread calls futex_lock_pi() */ pthread_barrier_wait(&barrier); @@ -149,14 +139,13 @@ TEST(lock_pi) * time or your time machine) the monotonic clock value is always * smaller than realtime and the syscall will timeout immediately. */ - if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns)) - ksft_test_result_error("get_time error"); + GET_ABS_TIMEOUT(CLOCK_REALTIME, &to, timeout_ns); res = futex_lock_pi(&futex_pi, &to, 0, 0); - test_timeout(res, "futex_lock_pi realtime", ETIMEDOUT); + TEST_TIMEOUT(res, "futex_lock_pi realtime", ETIMEDOUT); /* Test operations that don't support FUTEX_CLOCK_REALTIME */ res = futex_lock_pi(&futex_pi, NULL, 0, FUTEX_CLOCK_REALTIME); - test_timeout(res, "futex_lock_pi invalid timeout flag", ENOSYS); + TEST_TIMEOUT(res, "futex_lock_pi invalid timeout flag", ENOSYS); } TEST(waitv) @@ -171,17 +160,18 @@ TEST(waitv) struct timespec to; int res; + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + /* futex_waitv with CLOCK_MONOTONIC */ - if (futex_get_abs_timeout(CLOCK_MONOTONIC, &to, timeout_ns)) - ksft_test_result_error("get_time error"); + GET_ABS_TIMEOUT(CLOCK_MONOTONIC, &to, timeout_ns); res = futex_waitv(&waitv, 1, 0, &to, CLOCK_MONOTONIC); - test_timeout(res, "futex_waitv monotonic", ETIMEDOUT); + TEST_TIMEOUT(res, "futex_waitv monotonic", ETIMEDOUT); /* futex_waitv with CLOCK_REALTIME */ - if (futex_get_abs_timeout(CLOCK_REALTIME, &to, timeout_ns)) - ksft_test_result_error("get_time error"); + GET_ABS_TIMEOUT(CLOCK_REALTIME, &to, timeout_ns); res = futex_waitv(&waitv, 1, 0, &to, CLOCK_REALTIME); - test_timeout(res, "futex_waitv realtime", ETIMEDOUT); + TEST_TIMEOUT(res, "futex_waitv realtime", ETIMEDOUT); } TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait_uninitialized_heap.c b/tools/testing/selftests/futex/functional/futex_wait_uninitialized_heap.c index b07d68a67f31..bbffc23e0006 100644 --- a/tools/testing/selftests/futex/functional/futex_wait_uninitialized_heap.c +++ b/tools/testing/selftests/futex/functional/futex_wait_uninitialized_heap.c @@ -17,17 +17,18 @@ * *****************************************************************************/ +#include <errno.h> +#include <libgen.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/futex.h> #include <sys/mman.h> #include <syscall.h> #include <sys/types.h> #include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <linux/futex.h> -#include <libgen.h> #include "futextest.h" #include "kselftest_harness.h" @@ -40,6 +41,7 @@ void *buf; void *wait_thread(void *arg) { + struct __test_metadata *_metadata = (struct __test_metadata *)arg; int res; child_ret = true; @@ -47,7 +49,8 @@ void *wait_thread(void *arg) child_blocked = 0; if (res != 0 && errno != EWOULDBLOCK) { - ksft_exit_fail_msg("futex failure\n"); + EXPECT_EQ(res, 0) + TH_LOG("futex failure: %s", strerror(errno)); child_ret = false; } pthread_exit(NULL); @@ -63,21 +66,23 @@ TEST(futex_wait_uninitialized_heap) buf = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); - if (buf == (void *)-1) - ksft_exit_fail_msg("mmap\n"); + ASSERT_NE(buf, MAP_FAILED) + TH_LOG("mmap failed: %s", strerror(errno)); - ret = pthread_create(&thr, NULL, wait_thread, NULL); - if (ret) - ksft_exit_fail_msg("pthread_create\n"); + ret = pthread_create(&thr, NULL, wait_thread, _metadata); + ASSERT_EQ(ret, 0) + TH_LOG("pthread_create failed"); - ksft_print_dbg_msg("waiting %dus for child to return\n", WAIT_US); + TH_LOG("waiting %dus for child to return", WAIT_US); usleep(WAIT_US); - if (child_blocked) - ksft_test_result_fail("child blocked in kernel\n"); + EXPECT_EQ(child_blocked, 0) + TH_LOG("child blocked in kernel"); + EXPECT_TRUE(child_ret) + TH_LOG("child error"); - if (!child_ret) - ksft_test_result_fail("child error\n"); + pthread_join(thr, NULL); + munmap(buf, page_size); } TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/functional/futex_wait_wouldblock.c b/tools/testing/selftests/futex/functional/futex_wait_wouldblock.c index 9ff936ecf164..ab039dda3e51 100644 --- a/tools/testing/selftests/futex/functional/futex_wait_wouldblock.c +++ b/tools/testing/selftests/futex/functional/futex_wait_wouldblock.c @@ -28,20 +28,20 @@ #define timeout_ns 100000 + TEST(futex_wait_wouldblock) { struct timespec to = {.tv_sec = 0, .tv_nsec = timeout_ns}; futex_t f1 = FUTEX_INITIALIZER; int res; - ksft_print_dbg_msg("Calling futex_wait on f1: %u @ %p with val=%u\n", f1, &f1, f1+1); + TH_LOG("Calling futex_wait on f1: %u @ %p with val=%u", f1, &f1, f1+1); res = futex_wait(&f1, f1+1, &to, FUTEX_PRIVATE_FLAG); - if (!res || errno != EWOULDBLOCK) { - ksft_test_result_fail("futex_wait returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_wait\n"); + EXPECT_EQ(res, -1) + TH_LOG("futex_wait returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EWOULDBLOCK) + TH_LOG("futex_wait returned unexpected errno: %d", errno); } } @@ -57,8 +57,11 @@ TEST(futex_waitv_wouldblock) }; int res; - if (clock_gettime(CLOCK_MONOTONIC, &to)) - ksft_exit_fail_msg("clock_gettime failed %d\n", errno); + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("clock_gettime failed"); to.tv_nsec += timeout_ns; @@ -67,14 +70,13 @@ TEST(futex_waitv_wouldblock) to.tv_nsec -= 1000000000; } - ksft_print_dbg_msg("Calling futex_waitv on f1: %u @ %p with val=%u\n", f1, &f1, f1+1); + TH_LOG("Calling futex_waitv on f1: %u @ %p with val=%u", f1, &f1, f1+1); res = futex_waitv(&waitv, 1, 0, &to, CLOCK_MONOTONIC); - if (!res || errno != EWOULDBLOCK) { - ksft_test_result_fail("futex_waitv returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_waitv\n"); + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EWOULDBLOCK) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); } } diff --git a/tools/testing/selftests/futex/functional/futex_waitv.c b/tools/testing/selftests/futex/functional/futex_waitv.c index b5ada9fdb26f..4858d5faeecf 100644 --- a/tools/testing/selftests/futex/functional/futex_waitv.c +++ b/tools/testing/selftests/futex/functional/futex_waitv.c @@ -25,24 +25,26 @@ static struct futex_waitv waitv[NR_FUTEXES]; u_int32_t futexes[NR_FUTEXES] = {0}; + void *waiterfn(void *arg) { + struct __test_metadata *_metadata = (struct __test_metadata *)arg; struct timespec to; int res; /* setting absolute timeout for futex2 */ - if (clock_gettime(CLOCK_MONOTONIC, &to)) - ksft_exit_fail_msg("gettime64 failed\n"); + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); to.tv_sec++; res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); if (res < 0) { - ksft_test_result_fail("futex_waitv returned: %d %s\n", - errno, strerror(errno)); - } else if (res != NR_FUTEXES - 1) { - ksft_test_result_fail("futex_waitv returned: %d, expecting %d\n", - res, NR_FUTEXES - 1); + EXPECT_EQ(res, NR_FUTEXES - 1) + TH_LOG("futex_waitv failed: %s", strerror(errno)); + } else { + EXPECT_EQ(res, NR_FUTEXES - 1) + TH_LOG("futex_waitv returned %d, expected %d", res, NR_FUTEXES - 1); } return NULL; @@ -53,6 +55,9 @@ TEST(private_waitv) pthread_t waiter; int res, i; + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + for (i = 0; i < NR_FUTEXES; i++) { waitv[i].uaddr = (uintptr_t)&futexes[i]; waitv[i].flags = FUTEX_32 | FUTEX_PRIVATE_FLAG; @@ -61,19 +66,14 @@ TEST(private_waitv) } /* Private waitv */ - if (pthread_create(&waiter, NULL, waiterfn, NULL)) - ksft_exit_fail_msg("pthread_create failed\n"); + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, _metadata), 0) + TH_LOG("pthread_create failed"); usleep(WAKE_WAIT_US); res = futex_wake(u64_to_ptr(waitv[NR_FUTEXES - 1].uaddr), 1, FUTEX_PRIVATE_FLAG); - if (res != 1) { - ksft_test_result_fail("futex_wake private returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_waitv private\n"); - } + EXPECT_EQ(res, 1) + TH_LOG("futex_wake private returned: %d %s", res, res < 0 ? strerror(errno) : ""); } TEST(shared_waitv) @@ -81,15 +81,18 @@ TEST(shared_waitv) pthread_t waiter; int res, i; + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + /* Shared waitv */ for (i = 0; i < NR_FUTEXES; i++) { int shm_id = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666); if (shm_id < 0) { if (errno == ENOSYS) - ksft_exit_skip("shmget syscall not supported\n"); - perror("shmget"); - exit(1); + SKIP(return, "shmget syscall not supported"); + ASSERT_GE(shm_id, 0) + TH_LOG("shmget failed"); } unsigned int *shared_data = shmat(shm_id, NULL, 0); @@ -101,19 +104,14 @@ TEST(shared_waitv) waitv[i].__reserved = 0; } - if (pthread_create(&waiter, NULL, waiterfn, NULL)) - ksft_exit_fail_msg("pthread_create failed\n"); + ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, _metadata), 0) + TH_LOG("pthread_create failed"); usleep(WAKE_WAIT_US); res = futex_wake(u64_to_ptr(waitv[NR_FUTEXES - 1].uaddr), 1, 0); - if (res != 1) { - ksft_test_result_fail("futex_wake shared returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_waitv shared\n"); - } + EXPECT_EQ(res, 1) + TH_LOG("futex_wake shared returned: %d %s", res, res < 0 ? strerror(errno) : ""); for (i = 0; i < NR_FUTEXES; i++) shmdt(u64_to_ptr(waitv[i].uaddr)); @@ -124,21 +122,24 @@ TEST(invalid_flag) struct timespec to; int res; + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + /* Testing a waiter without FUTEX_32 flag */ waitv[0].flags = FUTEX_PRIVATE_FLAG; - if (clock_gettime(CLOCK_MONOTONIC, &to)) - ksft_exit_fail_msg("gettime64 failed\n"); + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); to.tv_sec++; res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); - if (res == EINVAL) { - ksft_test_result_fail("futex_waitv private returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_waitv without FUTEX_32\n"); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); } } @@ -147,22 +148,25 @@ TEST(unaligned_address) struct timespec to; int res; + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + /* Testing a waiter with an unaligned address */ waitv[0].flags = FUTEX_PRIVATE_FLAG | FUTEX_32; waitv[0].uaddr = 1; - if (clock_gettime(CLOCK_MONOTONIC, &to)) - ksft_exit_fail_msg("gettime64 failed\n"); + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); to.tv_sec++; res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); - if (res == EINVAL) { - ksft_test_result_fail("futex_wake private returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_waitv with an unaligned address\n"); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); } } @@ -171,36 +175,39 @@ TEST(null_address) struct timespec to; int res; + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + /* Testing a NULL address for waiters.uaddr */ waitv[0].uaddr = 0x00000000; - if (clock_gettime(CLOCK_MONOTONIC, &to)) - ksft_exit_fail_msg("gettime64 failed\n"); + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); to.tv_sec++; res = futex_waitv(waitv, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); - if (res == EINVAL) { - ksft_test_result_fail("futex_waitv private returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_waitv NULL address in waitv.uaddr\n"); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); } /* Testing a NULL address for *waiters */ - if (clock_gettime(CLOCK_MONOTONIC, &to)) - ksft_exit_fail_msg("gettime64 failed\n"); + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); to.tv_sec++; res = futex_waitv(NULL, NR_FUTEXES, 0, &to, CLOCK_MONOTONIC); - if (res == EINVAL) { - ksft_test_result_fail("futex_waitv private returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_waitv NULL address in *waiters\n"); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); } } @@ -209,19 +216,22 @@ TEST(invalid_clockid) struct timespec to; int res; + if (!is_futex_waitv_supported()) + SKIP(return, "futex_waitv syscall not supported"); + /* Testing an invalid clockid */ - if (clock_gettime(CLOCK_MONOTONIC, &to)) - ksft_exit_fail_msg("gettime64 failed\n"); + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, &to), 0) + TH_LOG("gettime64 failed"); to.tv_sec++; res = futex_waitv(NULL, NR_FUTEXES, 0, &to, CLOCK_TAI); - if (res == EINVAL) { - ksft_test_result_fail("futex_waitv private returned: %d %s\n", - res ? errno : res, - res ? strerror(errno) : ""); - } else { - ksft_test_result_pass("futex_waitv invalid clockid\n"); + + EXPECT_EQ(res, -1) + TH_LOG("futex_waitv returned unexpected result: %d", res); + if (res == -1) { + EXPECT_EQ(errno, EINVAL) + TH_LOG("futex_waitv returned unexpected errno: %d", errno); } } diff --git a/tools/testing/selftests/futex/functional/robust_list.c b/tools/testing/selftests/futex/functional/robust_list.c index b3fab60181d5..87217c549361 100644 --- a/tools/testing/selftests/futex/functional/robust_list.c +++ b/tools/testing/selftests/futex/functional/robust_list.c @@ -25,24 +25,24 @@ #define _GNU_SOURCE #include "futextest.h" -#include "../../kselftest_harness.h" +#include "kselftest_harness.h" #include <dlfcn.h> #include <errno.h> #include <pthread.h> #include <signal.h> -#include <stdint.h> #include <stdatomic.h> #include <stdbool.h> #include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> #include <sys/auxv.h> #include <sys/mman.h> #include <sys/wait.h> #define STACK_SIZE (1024 * 1024) - #define FUTEX_TIMEOUT 3 - #define SLEEP_US 100 #if __SIZEOF_LONG__ == 8 @@ -71,30 +71,46 @@ static int sys_futex_robust_unlock(_Atomic(uint32_t) *uaddr, unsigned int op, in * Basic lock struct, contains just the futex word and the robust list element * Real implementations have also a *prev to easily walk in the list */ +typedef _Atomic(unsigned int) atomic_futex_t; + struct lock_struct { - _Atomic(unsigned int) futex; + atomic_futex_t futex; struct robust_list list; }; +struct child_args { + struct __test_metadata *_metadata; + void *arg; +}; + /* * Helper function to spawn a child thread. Returns -1 on error, pid on success */ -static int create_child(int (*fn)(void *arg), void *arg) +static int create_child(struct __test_metadata *_metadata, int (*fn)(void *arg), void *arg) { + struct child_args *cargs = malloc(sizeof(*cargs)); char *stack; pid_t pid; + if (!cargs) + return -1; + cargs->_metadata = _metadata; + cargs->arg = arg; + stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); - if (stack == MAP_FAILED) + if (stack == MAP_FAILED) { + free(cargs); return -1; + } stack += STACK_SIZE; - pid = clone(fn, stack, CLONE_VM | SIGCHLD, arg); - - if (pid == -1) + pid = clone(fn, stack, CLONE_VM | SIGCHLD, cargs); + if (pid == -1) { + free(cargs); return -1; + } return pid; } @@ -123,7 +139,7 @@ static int set_list(struct robust_list_head *head) */ static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, bool error_inject) { - _Atomic(unsigned int) *futex = &lock->futex; + atomic_futex_t *futex = &lock->futex; unsigned int zero = 0; pid_t tid = gettid(); int ret = -1; @@ -183,21 +199,21 @@ static int mutex_lock(struct lock_struct *lock, struct robust_list_head *head, b */ static int child_fn_lock(void *arg) { - struct lock_struct *lock = arg; + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + struct lock_struct *lock = cargs->arg; struct robust_list_head head; int ret; + free(cargs); + ret = set_list(&head); - if (ret) { - ksft_test_result_fail("set_robust_list error\n"); - return ret; - } + ASSERT_EQ(ret, 0) + TH_LOG("set_robust_list error"); ret = mutex_lock(lock, &head, false); - if (ret) { - ksft_test_result_fail("mutex_lock error\n"); - return ret; - } + ASSERT_EQ(ret, 0) + TH_LOG("mutex_lock error"); pthread_barrier_wait(&barrier); @@ -220,7 +236,7 @@ static int child_fn_lock(void *arg) TEST(test_robustness) { struct lock_struct lock = { .futex = 0 }; - _Atomic(unsigned int) *futex = &lock.futex; + atomic_futex_t *futex = &lock.futex; struct robust_list_head head; int ret, pid, wstatus; @@ -234,7 +250,7 @@ TEST(test_robustness) ret = pthread_barrier_init(&barrier, NULL, 2); ASSERT_EQ(ret, 0); - pid = create_child(&child_fn_lock, &lock); + pid = create_child(_metadata, &child_fn_lock, &lock); ASSERT_NE(pid, -1); pthread_barrier_wait(&barrier); @@ -251,9 +267,8 @@ TEST(test_robustness) wait(&wstatus); pthread_barrier_destroy(&barrier); - /* Pass only if the child hasn't return error */ - if (!WEXITSTATUS(wstatus)) - ksft_test_result_pass("%s\n", __func__); + EXPECT_EQ(WEXITSTATUS(wstatus), 0) + TH_LOG("child failed"); } /* @@ -279,8 +294,6 @@ TEST(test_set_robust_list_invalid_size) ret = set_robust_list(&head, 0); ASSERT_EQ(ret, -1); ASSERT_EQ(errno, EINVAL); - - ksft_test_result_pass("%s\n", __func__); } /* @@ -307,20 +320,20 @@ TEST(test_get_robust_list_self) ASSERT_EQ(ret, 0); ASSERT_EQ(get_head, &head2); ASSERT_EQ(head_size, len_ptr); - - ksft_test_result_pass("%s\n", __func__); } static int child_list(void *arg) { - struct robust_list_head *head = arg; + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + struct robust_list_head *head = cargs->arg; int ret; + free(cargs); + ret = set_robust_list(head, sizeof(*head)); - if (ret) { - ksft_test_result_fail("set_robust_list error\n"); - return -1; - } + ASSERT_EQ(ret, 0) + TH_LOG("set_robust_list error"); /* * After setting the list head, wait until the main thread can call @@ -350,7 +363,7 @@ TEST(test_get_robust_list_child) ret = pthread_barrier_init(&barrier2, NULL, 2); ASSERT_EQ(ret, 0); - tid = create_child(&child_list, &head); + tid = create_child(_metadata, &child_list, &head); ASSERT_NE(tid, -1); pthread_barrier_wait(&barrier); @@ -365,28 +378,27 @@ TEST(test_get_robust_list_child) pthread_barrier_destroy(&barrier); pthread_barrier_destroy(&barrier2); - /* Pass only if the child hasn't return error */ - if (!WEXITSTATUS(wstatus)) - ksft_test_result_pass("%s\n", __func__); + EXPECT_EQ(WEXITSTATUS(wstatus), 0) + TH_LOG("child failed"); } static int child_fn_lock_with_error(void *arg) { - struct lock_struct *lock = arg; + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + struct lock_struct *lock = cargs->arg; struct robust_list_head head; int ret; + free(cargs); + ret = set_list(&head); - if (ret) { - ksft_test_result_fail("set_robust_list error\n"); - return -1; - } + ASSERT_EQ(ret, 0) + TH_LOG("set_robust_list error"); ret = mutex_lock(lock, &head, true); - if (ret) { - ksft_test_result_fail("mutex_lock error\n"); - return -1; - } + ASSERT_EQ(ret, 0) + TH_LOG("mutex_lock error"); pthread_barrier_wait(&barrier); @@ -404,7 +416,7 @@ static int child_fn_lock_with_error(void *arg) TEST(test_set_list_op_pending) { struct lock_struct lock = { .futex = 0 }; - _Atomic(unsigned int) *futex = &lock.futex; + atomic_futex_t *futex = &lock.futex; struct robust_list_head head; int ret, wstatus; @@ -414,7 +426,7 @@ TEST(test_set_list_op_pending) ret = pthread_barrier_init(&barrier, NULL, 2); ASSERT_EQ(ret, 0); - ret = create_child(&child_fn_lock_with_error, &lock); + ret = create_child(_metadata, &child_fn_lock_with_error, &lock); ASSERT_NE(ret, -1); pthread_barrier_wait(&barrier); @@ -427,21 +439,21 @@ TEST(test_set_list_op_pending) wait(&wstatus); pthread_barrier_destroy(&barrier); - /* Pass only if the child hasn't return error */ - if (!WEXITSTATUS(wstatus)) - ksft_test_result_pass("%s\n", __func__); - else - ksft_test_result_fail("%s\n", __func__); + EXPECT_EQ(WEXITSTATUS(wstatus), 0) + TH_LOG("child failed"); } #define CHILD_NR 10 static int child_lock_holder(void *arg) { - struct lock_struct *locks = arg; + struct child_args *cargs = arg; + struct lock_struct *locks = cargs->arg; struct robust_list_head head; int i; + free(cargs); + set_list(&head); for (i = 0; i < CHILD_NR; i++) { @@ -460,22 +472,21 @@ static int child_lock_holder(void *arg) static int child_wait_lock(void *arg) { - struct lock_struct *lock = arg; + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + struct lock_struct *lock = cargs->arg; struct robust_list_head head; int ret; + free(cargs); + pthread_barrier_wait(&barrier2); ret = mutex_lock(lock, &head, false); + ASSERT_EQ(ret, 0) + TH_LOG("mutex_lock error"); - if (ret) { - ksft_test_result_fail("mutex_lock error\n"); - return -1; - } - - if (!(lock->futex & FUTEX_OWNER_DIED)) { - ksft_test_result_fail("futex not marked with FUTEX_OWNER_DIED\n"); - return -1; - } + ASSERT_TRUE(lock->futex & FUTEX_OWNER_DIED) + TH_LOG("futex not marked with FUTEX_OWNER_DIED"); return 0; } @@ -495,18 +506,20 @@ TEST(test_robust_list_multiple_elements) ret = pthread_barrier_init(&barrier2, NULL, CHILD_NR + 1); ASSERT_EQ(ret, 0); - pids[0] = create_child(&child_lock_holder, &locks); + pids[0] = create_child(_metadata, &child_lock_holder, &locks); + ASSERT_NE(pids[0], -1); /* Wait until the locker thread takes the look */ pthread_barrier_wait(&barrier); - for (i = 0; i < CHILD_NR; i++) - pids[i+1] = create_child(&child_wait_lock, &locks[i]); + for (i = 0; i < CHILD_NR; i++) { + pids[i+1] = create_child(_metadata, &child_wait_lock, &locks[i]); + ASSERT_NE(pids[i+1], -1); + } - /* Wait for all children to return */ + /* Wait for all children to return (holder + all waiters) */ ret = 0; - - for (i = 0; i < CHILD_NR; i++) { + for (i = 0; i < CHILD_NR + 1; i++) { waitpid(pids[i], &wstatus, 0); if (WEXITSTATUS(wstatus)) ret = -1; @@ -515,22 +528,23 @@ TEST(test_robust_list_multiple_elements) pthread_barrier_destroy(&barrier); pthread_barrier_destroy(&barrier2); - /* Pass only if the child hasn't return error */ - if (!ret) - ksft_test_result_pass("%s\n", __func__); + EXPECT_EQ(ret, 0) + TH_LOG("One or more children failed"); } static int child_circular_list(void *arg) { - static struct robust_list_head head; - struct lock_struct a, b, c; + struct child_args *cargs = arg; + struct __test_metadata *_metadata = cargs->_metadata; + static struct lock_struct a, b, c; + struct robust_list_head head; int ret; + free(cargs); + ret = set_list(&head); - if (ret) { - ksft_test_result_fail("set_list error\n"); - return -1; - } + ASSERT_EQ(ret, 0) + TH_LOG("set_list error"); head.list.next = &a.list; @@ -552,14 +566,15 @@ static int child_circular_list(void *arg) TEST(test_circular_list) { int wstatus; + pid_t pid; - create_child(child_circular_list, NULL); + pid = create_child(_metadata, child_circular_list, NULL); + ASSERT_NE(pid, -1); wait(&wstatus); - /* Pass only if the child hasn't return error */ - if (!WEXITSTATUS(wstatus)) - ksft_test_result_pass("%s\n", __func__); + EXPECT_EQ(WEXITSTATUS(wstatus), 0) + TH_LOG("child failed"); } /* diff --git a/tools/testing/selftests/futex/functional/run.sh b/tools/testing/selftests/futex/functional/run.sh index e88545c06d57..d1a681b798bd 100755 --- a/tools/testing/selftests/futex/functional/run.sh +++ b/tools/testing/selftests/futex/functional/run.sh @@ -51,3 +51,6 @@ echo echo ./futex_numa_mpol + +echo +./futex_lock_pi_exiting diff --git a/tools/testing/selftests/futex/include/futex2test.h b/tools/testing/selftests/futex/include/futex2test.h index 1f625b39948a..53e88b60ac6d 100644 --- a/tools/testing/selftests/futex/include/futex2test.h +++ b/tools/testing/selftests/futex/include/futex2test.h @@ -5,7 +5,9 @@ * Copyright 2021 Collabora Ltd. */ #include <linux/time_types.h> +#include <errno.h> #include <stdint.h> +#include <stdbool.h> #define u64_to_ptr(x) ((void *)(uintptr_t)(x)) @@ -96,3 +98,11 @@ static inline int futex2_wake(void *uaddr, int nr, unsigned int flags) { return syscall(__NR_futex_wake, uaddr, ~0U, nr, flags); } + +static inline bool is_futex_waitv_supported(void) +{ + struct timespec ts = {0, 0}; + int res = futex_waitv(NULL, 0, 0, &ts, CLOCK_MONOTONIC); + + return !(res < 0 && errno == ENOSYS); +} diff --git a/tools/testing/selftests/futex/include/futex_thread.h b/tools/testing/selftests/futex/include/futex_thread.h new file mode 100644 index 000000000000..a90882960264 --- /dev/null +++ b/tools/testing/selftests/futex/include/futex_thread.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _FUTEX_THREAD_H +#define _FUTEX_THREAD_H +#include <errno.h> +#include <pthread.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "kselftest_harness.h" + +#define USEC_PER_SEC 1000000L +#define WAIT_FOR_THREAD_SECS 1 +#define WAIT_FOR_THREAD_USECS (WAIT_FOR_THREAD_SECS * USEC_PER_SEC) +#define WAIT_THREAD_RETRIES 100 + +struct futex_thread { + pthread_t thread; + pthread_barrier_t barrier; + pid_t tid; + int (*threadfn)(void *arg); + void *arg; + int retval; +}; + +static inline int __wait_for_thread(FILE *fp, struct __test_metadata *_metadata) +{ + unsigned int sleep_time_us = WAIT_FOR_THREAD_USECS / WAIT_THREAD_RETRIES; + char buf[80] = ""; + + for (int i = 0; i < WAIT_THREAD_RETRIES; i++) { + if (!fgets(buf, sizeof(buf), fp)) + return EIO; + if (!strncmp(buf, "futex", 5)) + return 0; + usleep(sleep_time_us); + rewind(fp); + } + + TH_LOG("/proc/$PID/wchan contains \"%s\". Trying to continue.", buf); + return 0; +} + +static void *__futex_thread_fn(void *arg) +{ + struct futex_thread *t = arg; + + t->tid = gettid(); + pthread_barrier_wait(&t->barrier); + t->retval = t->threadfn(t->arg); + return NULL; +} + +/** + * futex_wait_for_thread - Wait for the child thread to sleep in the futex context + * @t: Thread handle. + * @_metadata: Test metadata for TH_LOG() context + */ +static inline int futex_wait_for_thread(struct futex_thread *t, struct __test_metadata *_metadata) +{ + char fname[80]; + FILE *fp; + int res; + + snprintf(fname, sizeof(fname), "/proc/%d/wchan", t->tid); + fp = fopen(fname, "r"); + if (!fp) { + /* If /proc/... is not available, sleep */ + if (errno != ENOENT) + return errno; + TH_LOG("/proc/$PID/wchan not accessible, continue with sleep()"); + sleep(WAIT_FOR_THREAD_SECS); + return 0; + } + + res = __wait_for_thread(fp, _metadata); + fclose(fp); + return res; +} + +/** + * futex_thread_create - Create a new thread for testing. + * @t: The handle of the newly created thread. + * @threadfn: The new thread starts execution by invoking threadfn + * @arg: The parameters passed to threadfn. + */ +static inline int futex_thread_create(struct futex_thread *t, int (*threadfn)(void *), void *arg) +{ + pthread_barrier_init(&t->barrier, NULL, 2); + + t->tid = 0; + t->threadfn = threadfn; + t->arg = arg; + + if (pthread_create(&t->thread, NULL, __futex_thread_fn, t) < 0) { + int ret = errno; + pthread_barrier_destroy(&t->barrier); + return ret; + } + + pthread_barrier_wait(&t->barrier); + return 0; +} + +/** + * futex_thread_destroy - Wait for and reclaim the resources of the thread. + * @t: Thread handle. + */ +static inline int futex_thread_destroy(struct futex_thread *t) +{ + pthread_join(t->thread, NULL); + pthread_barrier_destroy(&t->barrier); + return t->retval; +} + +#endif diff --git a/tools/testing/selftests/gpio/.gitignore b/tools/testing/selftests/gpio/.gitignore index ededb077a3a6..16f74de479f1 100644 --- a/tools/testing/selftests/gpio/.gitignore +++ b/tools/testing/selftests/gpio/.gitignore @@ -2,3 +2,4 @@ gpio-mockup-cdev gpio-chip-info gpio-line-name +gpio-cdev-uaf diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile index 96071b4800e8..2f423de83147 100644 --- a/tools/testing/selftests/hid/Makefile +++ b/tools/testing/selftests/hid/Makefile @@ -24,7 +24,7 @@ CXX ?= $(CROSS_COMPILE)g++ HOSTPKG_CONFIG := pkg-config -CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT) +CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT) $(KHDR_INCLUDES) CFLAGS += -I$(OUTPUT)/tools/include LDLIBS += -lelf -lz -lrt -lpthread diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 1e979fb3542b..b851339308c2 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -86,6 +86,20 @@ static void load_programs(const struct test_program programs[], self->skel = hid__open(); ASSERT_OK_PTR(self->skel) TEARDOWN_LOG("Error while calling hid__open"); + /* + * Disable all struct_ops maps by default so libbpf does not autoload + * programs referenced by maps that are unrelated to the current test. + */ + bpf_object__for_each_map(iter_map, *self->skel->skeleton->obj) { + if (bpf_map__type(iter_map) == BPF_MAP_TYPE_STRUCT_OPS) { + err = bpf_map__set_autocreate(iter_map, false); + ASSERT_OK(err) TH_LOG("can not disable struct_ops map '%s'", + bpf_map__name(iter_map)); + } + + bpf_map__set_autoattach(iter_map, false); + } + for (int i = 0; i < progs_count; i++) { struct bpf_program *prog; struct bpf_map *map; @@ -102,6 +116,10 @@ static void load_programs(const struct test_program programs[], ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'", programs[i].name + 4); + err = bpf_map__set_autocreate(map, true); + ASSERT_OK(err) TH_LOG("can not enable struct_ops map '%s'", + programs[i].name + 4); + /* hid_id is the first field of struct hid_bpf_ops */ ops_hid_id = bpf_map__initial_value(map, NULL); ASSERT_OK_PTR(ops_hid_id) TH_LOG("unable to retrieve struct_ops data"); @@ -109,13 +127,6 @@ static void load_programs(const struct test_program programs[], *ops_hid_id = self->hid.hid_id; } - /* we disable the auto-attach feature of all maps because we - * only want the tested one to be manually attached in the next - * call to bpf_map__attach_struct_ops() - */ - bpf_object__for_each_map(iter_map, *self->skel->skeleton->obj) - bpf_map__set_autoattach(iter_map, false); - err = hid__load(self->skel); ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err); @@ -887,6 +898,17 @@ TEST_F(hid_bpf, test_rdesc_fixup) ASSERT_EQ(rpt_desc.value[4], 0x42); } +TEST_F(hid_bpf, test_rdesc_fixup_get_data_overflow) +{ + const struct test_program progs[] = { + { .name = "hid_rdesc_fixup_get_data_overflow" }, + }; + + LOAD_PROGRAMS(progs); + + ASSERT_EQ(self->skel->bss->get_data_overflow_check, 1); +} + static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) { diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c index 5ecc845ef792..b21fbb13c926 100644 --- a/tools/testing/selftests/hid/progs/hid.c +++ b/tools/testing/selftests/hid/progs/hid.c @@ -13,6 +13,7 @@ struct attach_prog_args { __u64 callback_check = 52; __u64 callback2_check = 52; +__u64 get_data_overflow_check; SEC("?struct_ops/hid_device_event") int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) @@ -240,6 +241,20 @@ struct hid_bpf_ops rdesc_fixup = { .hid_rdesc_fixup = (void *)hid_rdesc_fixup, }; +SEC("?struct_ops.s/hid_rdesc_fixup") +int BPF_PROG(hid_rdesc_fixup_get_data_overflow, struct hid_bpf_ctx *hid_ctx) +{ + if (!hid_bpf_get_data(hid_ctx, 2 /* offset */, ~0ULL /* size */)) + get_data_overflow_check = 1; + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops rdesc_fixup_get_data_overflow = { + .hid_rdesc_fixup = (void *)hid_rdesc_fixup_get_data_overflow, +}; + SEC("?struct_ops/hid_device_event") int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) { diff --git a/tools/testing/selftests/hid/tests/test_multitouch.py b/tools/testing/selftests/hid/tests/test_multitouch.py index fa4fb2054bd4..7897340118b4 100644 --- a/tools/testing/selftests/hid/tests/test_multitouch.py +++ b/tools/testing/selftests/hid/tests/test_multitouch.py @@ -513,6 +513,79 @@ class SmartTechDigitizer(Digitizer): return absinfo is not None and absinfo.resolution == 3 +class MinWin8TSParallelBigContactMax(Digitizer): + """A parallel Win8 touchscreen that advertises a ContactCountMaximum much + larger than the number of contacts it actually reports. + + Such firmware makes the driver allocate that many input slots (up to 255) + while the input report only carries a few contacts. This is what used to + drive the per-slot bit operations on mt_io_flags out of bounds. The number + of contacts a HID report can describe is limited by the descriptor size, + so a large ContactCountMaximum can only be expressed this way, decoupled + from the number of finger collections.""" + + def __init__(self, n_fingers=5, contact_max=250): + self.phys_max = 120, 90 + rdesc_finger_str = f""" + Usage Page (Digitizers) + Usage (Finger) + Collection (Logical) + Report Size (1) + Report Count (1) + Logical Minimum (0) + Logical Maximum (1) + Usage (Tip Switch) + Input (Data,Var,Abs) + Report Size (7) + Logical Maximum (127) + Input (Cnst,Var,Abs) + Report Size (8) + Logical Maximum (255) + Usage (Contact Id) + Input (Data,Var,Abs) + Report Size (16) + Unit Exponent (-1) + Unit (SILinear: cm) + Logical Maximum (4095) + Physical Minimum (0) + Physical Maximum ({self.phys_max[0]}) + Usage Page (Generic Desktop) + Usage (X) + Input (Data,Var,Abs) + Physical Maximum ({self.phys_max[1]}) + Usage (Y) + Input (Data,Var,Abs) + End Collection +""" + rdesc_str = f""" + Usage Page (Digitizers) + Usage (Touch Screen) + Collection (Application) + Report ID (1) + {rdesc_finger_str * n_fingers} + Unit Exponent (-4) + Unit (SILinear: s) + Logical Maximum (65535) + Physical Maximum (65535) + Usage Page (Digitizers) + Usage (Scan Time) + Input (Data,Var,Abs) + Report Size (8) + Logical Maximum (255) + Usage (Contact Count) + Input (Data,Var,Abs) + Report ID (2) + Logical Maximum ({contact_max}) + Usage (Contact Max) + Feature (Data,Var,Abs) + End Collection + {Digitizer.msCertificationBlob(68)} +""" + super().__init__( + f"uhid test parallel big contact max {contact_max}", rdesc_str + ) + + class BaseTest: class TestMultitouch(base.BaseTestCase.TestUhid): kernel_modules = [KERNEL_MODULE] @@ -1735,6 +1808,47 @@ class TestMinWin8TSParallel(BaseTest.TestWin8Multitouch): return MinWin8TSParallel(10) +class TestMinWin8TSParallelBigContactMax(base.BaseTestCase.TestUhid): + """Regression test for the out-of-bounds bit operations on + struct mt_device.mt_io_flags. + + A Win8 touchscreen may advertise a ContactCountMaximum much larger than + the number of contacts it reports. The driver used to keep the per-slot + active state in the bits of a single unsigned long while indexing + set_bit()/clear_bit() by the slot number, so such a device drove those bit + operations out of bounds. The sticky-fingers release timer made it fatal: + mt_release_contacts() cleared one bit per slot, overwrote the adjacent + struct mt_device members and panicked the kernel. + + Send a single contact, let the 100ms sticky-fingers timer release it, and + check that the kernel reports the release cleanly instead of crashing.""" + + kernel_modules = [KERNEL_MODULE] + + def create_device(self): + return MinWin8TSParallelBigContactMax() + + def test_sticky_fingers_release_big_contact_max(self): + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + assert evdev.num_slots == uhdev.max_contacts + + t0 = Touch(1, 5, 10) + r = uhdev.event([t0]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0 + + # do not release the contact; the sticky-fingers timer must do it + # after 100ms, which is where the out-of-bounds release used to hit + time.sleep(0.2) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events + assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 + + class TestMinWin8TSHybrid(BaseTest.TestWin8Multitouch): def create_device(self): return MinWin8TSHybrid() diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index d28a057fa6c2..6fc34e9bf8e1 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -174,6 +174,7 @@ TEST_GEN_PROGS_arm64 += arm64/hello_el2 TEST_GEN_PROGS_arm64 += arm64/host_sve TEST_GEN_PROGS_arm64 += arm64/hypercalls TEST_GEN_PROGS_arm64 += arm64/external_aborts +TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext TEST_GEN_PROGS_arm64 += arm64/page_fault_test TEST_GEN_PROGS_arm64 += arm64/psci_test TEST_GEN_PROGS_arm64 += arm64/sea_to_user diff --git a/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c b/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c new file mode 100644 index 000000000000..25196f1e6322 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/mmio_sign_ext.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmio_sign_ext - Test sign-extending MMIO load emulation (LDRSB/LDRSH/LDRSW) + * + * Copyright (c) 2026 Google LLC + * Author: Fuad Tabba <fuad.tabba@linux.dev> + */ + +#include <asm/ptrace.h> + +#include "processor.h" +#include "test_util.h" + +#define MMIO_ADDR 0x8000000ULL + +/* AP[1]: allow unprivileged (EL0) access to a mapping. */ +#define PTE_USER BIT(6) + +/* SPSR for ERET to EL0t with DAIF masked. */ +#define SPSR_EL0 (PSR_MODE_EL0t | PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT) + +struct mmio_test { + const char *name; + uint64_t data; /* access-width value, host byte order */ + uint8_t len; + uint64_t expected; /* sign-extended result; same for LE and BE */ +}; + +/* Paired 1:1, in order, with the loads in guest_loads_le() and el0_be_loads. */ +static const struct mmio_test tests[] = { + /* LDRSB Xt: byte sign-extended to 64 bits */ + { "LDRSB Xt 0xFF", 0xFF, 1, 0xFFFFFFFFFFFFFFFFULL }, + { "LDRSB Xt 0x7F", 0x7F, 1, 0x7FULL }, + + /* LDRSB Wt: byte sign-extended to 32 bits, upper 32 bits zeroed */ + { "LDRSB Wt 0xFF", 0xFF, 1, 0xFFFFFFFFULL }, + { "LDRSB Wt 0x7F", 0x7F, 1, 0x7FULL }, + + /* LDRSH Xt: halfword sign-extended to 64 bits */ + { "LDRSH Xt 0x8001", 0x8001, 2, 0xFFFFFFFFFFFF8001ULL }, + { "LDRSH Xt 0x7FFF", 0x7FFF, 2, 0x7FFFULL }, + + /* LDRSH Wt: halfword sign-extended to 32 bits, upper 32 bits zeroed */ + { "LDRSH Wt 0x8001", 0x8001, 2, 0xFFFF8001ULL }, + { "LDRSH Wt 0x7FFF", 0x7FFF, 2, 0x7FFFULL }, + + /* LDRSW Xt: word sign-extended to 64 bits (no Wt form) */ + { "LDRSW Xt 0x80000001", 0x80000001, 4, 0xFFFFFFFF80000001ULL }, + { "LDRSW Xt 0x7FFFFFFF", 0x7FFFFFFF, 4, 0x7FFFFFFFULL }, +}; + +/* Issue one sign-extending load from MMIO and report the result. */ +#define GUEST_LDRS(load) do { \ + uint64_t val; \ + \ + asm volatile(load : "=r"(val) : "r"(MMIO_ADDR) : "memory"); \ + GUEST_SYNC(val); \ +} while (0) + +/* Little-endian pass: loads issued at EL1. */ +static void guest_loads_le(void) +{ + GUEST_LDRS("ldrsb %0, [%1]"); + GUEST_LDRS("ldrsb %0, [%1]"); + GUEST_LDRS("ldrsb %w0, [%1]"); + GUEST_LDRS("ldrsb %w0, [%1]"); + GUEST_LDRS("ldrsh %0, [%1]"); + GUEST_LDRS("ldrsh %0, [%1]"); + GUEST_LDRS("ldrsh %w0, [%1]"); + GUEST_LDRS("ldrsh %w0, [%1]"); + GUEST_LDRS("ldrsw %0, [%1]"); + GUEST_LDRS("ldrsw %0, [%1]"); +} + +/* + * Run the big-endian loads at EL0, where SCTLR_EL1.E0E flips only the data + * endianness; at EL1, SCTLR_EL1.EE would also flip the page-table walk and + * fault on the little-endian tables. x0 holds MMIO_ADDR; results return in + * x19..x28 (tests[] order) via a single SVC. + */ +extern char el0_be_loads[]; +asm( +" .pushsection .text, \"ax\"\n" +" .global el0_be_loads\n" +"el0_be_loads:\n" +" ldrsb x19, [x0]\n" +" ldrsb x20, [x0]\n" +" ldrsb w21, [x0]\n" +" ldrsb w22, [x0]\n" +" ldrsh x23, [x0]\n" +" ldrsh x24, [x0]\n" +" ldrsh w25, [x0]\n" +" ldrsh w26, [x0]\n" +" ldrsw x27, [x0]\n" +" ldrsw x28, [x0]\n" +" svc #0\n" +" .popsection\n" +); + +/* EL1 handler for the EL0 SVC: report the results, then finish. */ +static void el0_svc_handler(struct ex_regs *regs) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tests); i++) + GUEST_SYNC(regs->regs[19 + i]); + + GUEST_DONE(); +} + +static bool guest_mixed_endian_el0(void) +{ + uint64_t mmfr0 = read_sysreg(id_aa64mmfr0_el1); + + return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) || + SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0); +} + +static void guest_code(void) +{ + guest_loads_le(); + + if (guest_mixed_endian_el0()) { + write_sysreg(read_sysreg(sctlr_el1) | SCTLR_EL1_E0E, sctlr_el1); + isb(); + + asm volatile( + " msr elr_el1, %[pc]\n" + " msr spsr_el1, %[spsr]\n" + " mov x0, %[mmio]\n" + " isb\n" + " eret\n" + : + : [pc] "r"(el0_be_loads), + [spsr] "r"((uint64_t)SPSR_EL0), + [mmio] "r"(MMIO_ADDR) + : "x0", "memory"); + __builtin_unreachable(); /* el0_svc_handler ends the test */ + } + + GUEST_DONE(); +} + +static void handle_mmio(struct kvm_run *run, const struct mmio_test *t, bool be) +{ + int i; + + TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR); + TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read for %s", t->name); + TEST_ASSERT_EQ(run->mmio.len, t->len); + + memset(run->mmio.data, 0, sizeof(run->mmio.data)); + if (be) { + /* The guest reads the device bytes most-significant first. */ + for (i = 0; i < t->len; i++) + run->mmio.data[i] = t->data >> (8 * (t->len - 1 - i)); + } else { + /* Works because arm64 KVM hosts are always little-endian. */ + memcpy(run->mmio.data, &t->data, t->len); + } +} + +static void expect_sync(struct kvm_vcpu *vcpu, struct ucall *uc, + const struct mmio_test *t) +{ + switch (get_ucall(vcpu, uc)) { + case UCALL_SYNC: + TEST_ASSERT(uc->args[1] == t->expected, + "%s: got %#lx, want %#lx", t->name, + (unsigned long)uc->args[1], (unsigned long)t->expected); + break; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(*uc); + break; + default: + TEST_FAIL("Unexpected ucall for %s", t->name); + } +} + +/* OR PTE_USER into the leaf descriptors covering [gva, gva + len). */ +static void make_el0_accessible(struct kvm_vm *vm, uint64_t gva, uint64_t len) +{ + uint64_t addr; + + for (addr = gva & ~((uint64_t)vm->page_size - 1); addr < gva + len; + addr += vm->page_size) + *virt_get_pte_hva(vm, addr) |= PTE_USER; +} + +static bool vcpu_mixed_endian_el0(struct kvm_vcpu *vcpu) +{ + uint64_t mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1)); + + return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) || + SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0); +} + +int main(void) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + unsigned int i; + bool be; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code); + virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1); + + vm_init_descriptor_tables(vm); + vcpu_init_descriptor_tables(vcpu); + vm_install_sync_handler(vm, VECTOR_SYNC_LOWER_64, ESR_ELx_EC_SVC64, + el0_svc_handler); + + be = vcpu_mixed_endian_el0(vcpu); + if (be) + make_el0_accessible(vm, MMIO_ADDR, vm->page_size); + + ksft_print_header(); + ksft_set_plan(ARRAY_SIZE(tests) * (be ? 2 : 1)); + + /* Little-endian pass: one load and one result per iteration. */ + for (i = 0; i < ARRAY_SIZE(tests); i++) { + const struct mmio_test *t = &tests[i]; + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); + handle_mmio(vcpu->run, t, false); + + vcpu_run(vcpu); + expect_sync(vcpu, &uc, t); + + ksft_test_result_pass("%s\n", t->name); + } + + if (be) { + /* The EL0 stub issues all the loads, then reports the results. */ + for (i = 0; i < ARRAY_SIZE(tests); i++) { + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); + handle_mmio(vcpu->run, &tests[i], true); + } + for (i = 0; i < ARRAY_SIZE(tests); i++) { + vcpu_run(vcpu); + expect_sync(vcpu, &uc, &tests[i]); + ksft_test_result_pass("BE %s\n", tests[i].name); + } + } + + vcpu_run(vcpu); + TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_DONE, "Expected UCALL_DONE"); + + kvm_vm_free(vm); + + ksft_finished(); +} diff --git a/tools/testing/selftests/kvm/x86/sev_init2_tests.c b/tools/testing/selftests/kvm/x86/sev_init2_tests.c index 8db88c355f16..689390c10f7c 100644 --- a/tools/testing/selftests/kvm/x86/sev_init2_tests.c +++ b/tools/testing/selftests/kvm/x86/sev_init2_tests.c @@ -130,12 +130,18 @@ int main(int argc, char *argv[]) KVM_X86_SEV_VMSA_FEATURES, &supported_vmsa_features); - have_sev = kvm_cpu_has(X86_FEATURE_SEV); - TEST_ASSERT(have_sev == !!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM)), - "sev: KVM_CAP_VM_TYPES (%x) does not match cpuid (checking %x)", - kvm_check_cap(KVM_CAP_VM_TYPES), 1 << KVM_X86_SEV_VM); + /* + * Whether a VM type is available depends on KVM, not just CPUID: e.g. + * when all SEV ASIDs are assigned to SEV-SNP, KVM does not offer the + * SEV VM type even though X86_FEATURE_SEV is set. Derive availability + * from KVM_CAP_VM_TYPES and only assert the one-way implication that a + * type offered by KVM must also be reported in CPUID. + */ + have_sev = kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM); + TEST_ASSERT(!have_sev || kvm_cpu_has(X86_FEATURE_SEV), + "sev: SEV_VM supported without SEV in CPUID"); - TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM)); + TEST_REQUIRE(have_sev); have_sev_es = kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_ES_VM); TEST_ASSERT(!have_sev_es || kvm_cpu_has(X86_FEATURE_SEV_ES), diff --git a/tools/testing/selftests/kvm/x86/sev_migrate_tests.c b/tools/testing/selftests/kvm/x86/sev_migrate_tests.c index 42bc023d5193..d59abb198d86 100644 --- a/tools/testing/selftests/kvm/x86/sev_migrate_tests.c +++ b/tools/testing/selftests/kvm/x86/sev_migrate_tests.c @@ -313,6 +313,49 @@ out: kvm_vm_free(vm_no_vcpu); } +static void test_sev_snp_migrate_reject(void) +{ + struct kvm_vm *src_vm, *dst_vm; + int ret; + + src_vm = vm_create_barebones_type(KVM_X86_SNP_VM); + snp_vm_init(src_vm); + __vm_vcpu_add(src_vm, 0); + vm_sev_launch(src_vm, snp_default_policy(), NULL); + + dst_vm = vm_create_barebones_type(KVM_X86_SNP_VM); + __vm_vcpu_add(dst_vm, 0); + + ret = __sev_migrate_from(dst_vm, src_vm); + TEST_ASSERT(ret == -1 && errno == EINVAL, + "SNP VM migration should be rejected. ret: %d, errno: %d", + ret, errno); + + kvm_vm_free(src_vm); + kvm_vm_free(dst_vm); +} + +static void test_sev_snp_mirror_reject(void) +{ + struct kvm_vm *src_vm, *dst_vm; + int ret; + + src_vm = vm_create_barebones_type(KVM_X86_SNP_VM); + snp_vm_init(src_vm); + __vm_vcpu_add(src_vm, 0); + vm_sev_launch(src_vm, snp_default_policy(), NULL); + + dst_vm = aux_vm_create(false); + + ret = __sev_mirror_create(dst_vm, src_vm); + TEST_ASSERT(ret == -1 && errno == EINVAL, + "SNP VM mirroring should be rejected. ret: %d, errno: %d", + ret, errno); + + kvm_vm_free(src_vm); + kvm_vm_free(dst_vm); +} + static void test_sev_move_copy(void) { struct kvm_vm *dst_vm, *dst2_vm, *dst3_vm, *sev_vm, *mirror_vm, @@ -384,12 +427,16 @@ int main(int argc, char *argv[]) test_sev_migrate_parameters(); if (kvm_has_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM)) test_sev_move_copy(); + if (kvm_cpu_has(X86_FEATURE_SEV_SNP)) + test_sev_snp_migrate_reject(); } if (kvm_has_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM)) { test_sev_mirror(/* es= */ false); if (have_sev_es) test_sev_mirror(/* es= */ true); test_sev_mirror_parameters(); + if (kvm_cpu_has(X86_FEATURE_SEV_SNP)) + test_sev_snp_mirror_reject(); } return 0; } diff --git a/tools/testing/selftests/kvm/x86/sev_smoke_test.c b/tools/testing/selftests/kvm/x86/sev_smoke_test.c index 6b2cbe2a90b7..bf27b6187afa 100644 --- a/tools/testing/selftests/kvm/x86/sev_smoke_test.c +++ b/tools/testing/selftests/kvm/x86/sev_smoke_test.c @@ -247,7 +247,14 @@ int main(int argc, char *argv[]) { TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SEV)); - test_sev_smoke(guest_sev_code, KVM_X86_SEV_VM, 0); + /* + * Only exercise VM types the host actually offers. CPUID reporting + * SEV does not guarantee KVM offers the SEV VM type: when all SEV + * ASIDs are assigned to SEV-SNP, KVM_X86_SEV_VM is unavailable even + * though X86_FEATURE_SEV is set. Gate every type on KVM_CAP_VM_TYPES. + */ + if (kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM)) + test_sev_smoke(guest_sev_code, KVM_X86_SEV_VM, 0); if (kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_ES_VM)) test_sev_smoke(guest_sev_es_code, KVM_X86_SEV_ES_VM, SEV_POLICY_ES); diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 2ed1f76b7a8b..be2eb88092fb 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -1281,6 +1281,103 @@ TEST_F(protocol, connect_unspec) EXPECT_EQ(0, close(bind_fd)); } +TEST_F(protocol, tcp_fastopen) +{ + const bool restricted = variant->sandbox == TCP_SANDBOX && + variant->prot.type == SOCK_STREAM && + (variant->prot.protocol == IPPROTO_TCP || + variant->prot.protocol == IPPROTO_IP) && + (variant->prot.domain == AF_INET || + variant->prot.domain == AF_INET6); + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + int bind_fd, client_fd, status; + char buf; + pid_t child; + + bind_fd = socket_variant(&self->srv0); + ASSERT_LE(0, bind_fd); + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0)); + if (self->srv0.protocol.type == SOCK_STREAM) + EXPECT_EQ(0, listen(bind_fd, backlog)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int connect_fd, ret; + + /* Closes listening socket for the child. */ + EXPECT_EQ(0, close(bind_fd)); + + connect_fd = socket_variant(&self->srv0); + ASSERT_LE(0, connect_fd); + + if (variant->sandbox == TCP_SANDBOX) { + const int ruleset_fd = landlock_create_ruleset( + &ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + /* Fast Open with no address. */ + ret = sendto_variant(connect_fd, NULL, NULL, 0, MSG_FASTOPEN); + if (self->srv0.protocol.domain == AF_UNIX) { + EXPECT_EQ(-ENOTCONN, ret); + } else if (self->srv0.protocol.type == SOCK_DGRAM) { + EXPECT_EQ(-EDESTADDRREQ, ret); + } else { + EXPECT_EQ(-EINVAL, ret); + } + + /* Fast Open to a denied address. */ + ret = sendto_variant(connect_fd, &self->srv0, "A", 1, + MSG_FASTOPEN); + if (restricted) { + EXPECT_EQ(-EACCES, ret); + } else if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-EOPNOTSUPP, ret); + } else { + EXPECT_EQ(0, ret); + } + + EXPECT_EQ(0, close(connect_fd)); + _exit(_metadata->exit_code); + return; + } + + client_fd = bind_fd; + if (!restricted && self->srv0.protocol.type == SOCK_STREAM && + self->srv0.protocol.domain != AF_UNIX) { + client_fd = accept(bind_fd, NULL, 0); + ASSERT_LE(0, client_fd); + } + + if (restricted) { + EXPECT_EQ(-1, read(client_fd, &buf, 1)); + EXPECT_EQ(ENOTCONN, errno); + } else if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-1, read(client_fd, &buf, 1)); + EXPECT_EQ(EINVAL, errno); + } else { + EXPECT_EQ(1, read(client_fd, &buf, 1)); + EXPECT_EQ('A', buf); + } + + EXPECT_EQ(child, waitpid(child, &status, 0)); + EXPECT_EQ(1, WIFEXITED(status)); + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + if (client_fd != bind_fd) + EXPECT_LE(0, close(client_fd)); + + EXPECT_EQ(0, close(bind_fd)); +} + TEST_F(protocol, sendmsg_stream) { int srv0_fd, tmp_fd, client_fd, res; diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c index f24f2c28f62e..2d37d0c06c06 100644 --- a/tools/testing/selftests/landlock/scoped_signal_test.c +++ b/tools/testing/selftests/landlock/scoped_signal_test.c @@ -249,12 +249,12 @@ TEST_F(scoped_domains, check_access_signal) _metadata->exit_code = KSFT_FAIL; } -enum thread_return { - THREAD_INVALID = 0, - THREAD_SUCCESS = 1, - THREAD_ERROR = 2, - THREAD_TEST_FAILED = 3, -}; +/* clang-format off */ +#define THREAD_INVALID ((void *)0) +#define THREAD_SUCCESS ((void *)1) +#define THREAD_ERROR ((void *)2) +#define THREAD_TEST_FAILED ((void *)3) +/* clang-format on */ static void *thread_sync(void *arg) { @@ -262,15 +262,15 @@ static void *thread_sync(void *arg) char buf; if (read(pipe_read, &buf, 1) != 1) - return (void *)THREAD_ERROR; + return THREAD_ERROR; - return (void *)THREAD_SUCCESS; + return THREAD_SUCCESS; } TEST(signal_scoping_thread_before) { pthread_t no_sandbox_thread; - enum thread_return ret = THREAD_INVALID; + void *ret = THREAD_INVALID; int thread_pipe[2]; drop_caps(_metadata); @@ -285,7 +285,7 @@ TEST(signal_scoping_thread_before) EXPECT_EQ(0, pthread_kill(no_sandbox_thread, 0)); EXPECT_EQ(1, write(thread_pipe[1], ".", 1)); - EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret)); + EXPECT_EQ(0, pthread_join(no_sandbox_thread, &ret)); EXPECT_EQ(THREAD_SUCCESS, ret); EXPECT_EQ(0, close(thread_pipe[0])); @@ -295,7 +295,7 @@ TEST(signal_scoping_thread_before) TEST(signal_scoping_thread_after) { pthread_t scoped_thread; - enum thread_return ret = THREAD_INVALID; + void *ret = THREAD_INVALID; int thread_pipe[2]; drop_caps(_metadata); @@ -310,7 +310,7 @@ TEST(signal_scoping_thread_after) EXPECT_EQ(0, pthread_kill(scoped_thread, 0)); EXPECT_EQ(1, write(thread_pipe[1], ".", 1)); - EXPECT_EQ(0, pthread_join(scoped_thread, (void **)&ret)); + EXPECT_EQ(0, pthread_join(scoped_thread, &ret)); EXPECT_EQ(THREAD_SUCCESS, ret); EXPECT_EQ(0, close(thread_pipe[0])); @@ -327,20 +327,20 @@ void *thread_setuid(void *ptr) char buf; if (read(arg->pipe_read, &buf, 1) != 1) - return (void *)THREAD_ERROR; + return THREAD_ERROR; /* libc's setuid() should update all thread's credentials. */ if (getuid() != arg->new_uid) - return (void *)THREAD_TEST_FAILED; + return THREAD_TEST_FAILED; - return (void *)THREAD_SUCCESS; + return THREAD_SUCCESS; } TEST(signal_scoping_thread_setuid) { struct thread_setuid_args arg; pthread_t no_sandbox_thread; - enum thread_return ret = THREAD_INVALID; + void *ret = THREAD_INVALID; int pipe_parent[2]; int prev_uid; @@ -367,7 +367,7 @@ TEST(signal_scoping_thread_setuid) EXPECT_EQ(arg.new_uid, getuid()); EXPECT_EQ(1, write(pipe_parent[1], ".", 1)); - EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret)); + EXPECT_EQ(0, pthread_join(no_sandbox_thread, &ret)); EXPECT_EQ(THREAD_SUCCESS, ret); clear_cap(_metadata, CAP_SETUID); @@ -400,6 +400,24 @@ static int setup_signal_handler(int signal) return sigaction(SIGURG, &sa, NULL); } +/* + * MSG_OOB might be disabled in the kernel via the CONFIG_AF_UNIX_OOB + * switch, so this function can be used for probing for its availability. + */ +static bool has_af_unix_oob(void) +{ + bool available = false; + int sp[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) == 0) { + available = (send(sp[0], ".", 1, MSG_OOB) == 1); + close(sp[0]); + close(sp[1]); + } + + return available; +} + /* clang-format off */ FIXTURE(fown) {}; /* clang-format on */ @@ -462,6 +480,9 @@ TEST_F(fown, sigurg_socket) int pipe_parent[2], pipe_child[2]; pid_t child; + if (!has_af_unix_oob()) + SKIP(return, "CONFIG_AF_UNIX_OOB / MSG_OOB not available"); + memset(&server_address, 0, sizeof(server_address)); set_unix_address(&server_address, 0); @@ -667,20 +688,20 @@ static void *thread_setown_scoped(void *arg) ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); if (ruleset_fd < 0) - return (void *)THREAD_ERROR; + return THREAD_ERROR; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) || landlock_restrict_self(ruleset_fd, 0)) { close(ruleset_fd); - return (void *)THREAD_ERROR; + return THREAD_ERROR; } close(ruleset_fd); /* Makes this process group own the SIGIO source. */ if (fcntl(fd, F_SETSIG, SIGURG) || fcntl(fd, F_SETOWN, -getpgrp()) || fcntl(fd, F_SETFL, O_ASYNC)) - return (void *)THREAD_ERROR; + return THREAD_ERROR; - return (void *)THREAD_SUCCESS; + return THREAD_SUCCESS; } /* @@ -702,7 +723,7 @@ TEST(sigio_to_pgid_self) { int trigger[2]; pthread_t thread; - enum thread_return ret = THREAD_INVALID; + void *ret = THREAD_INVALID; int i; drop_caps(_metadata); @@ -722,7 +743,7 @@ TEST(sigio_to_pgid_self) */ ASSERT_EQ(0, pthread_create(&thread, NULL, thread_setown_scoped, &trigger[0])); - ASSERT_EQ(0, pthread_join(thread, (void **)&ret)); + ASSERT_EQ(0, pthread_join(thread, &ret)); ASSERT_EQ(THREAD_SUCCESS, ret); /* Fans SIGURG out to the process group. */ diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore index 661827083ab6..47c670bf532b 100644 --- a/tools/testing/selftests/liveupdate/.gitignore +++ b/tools/testing/selftests/liveupdate/.gitignore @@ -3,7 +3,9 @@ !/**/ !*.c !*.h +!*.mk !*.sh !.gitignore !config +!config.* !Makefile diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile index 30689d22cb02..634211c66652 100644 --- a/tools/testing/selftests/liveupdate/Makefile +++ b/tools/testing/selftests/liveupdate/Makefile @@ -1,7 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -LIB_C += luo_test_utils.c - TEST_GEN_PROGS += liveupdate TEST_GEN_PROGS_EXTENDED += luo_kexec_simple @@ -12,25 +10,21 @@ TEST_GEN_PROGS_EXTENDED += luo_stress_files TEST_FILES += do_kexec.sh include ../lib.mk +include lib/libliveupdate.mk CFLAGS += $(KHDR_INCLUDES) CFLAGS += -Wall -O2 -Wno-unused-function CFLAGS += -MD -LIB_O := $(patsubst %.c, $(OUTPUT)/%.o, $(LIB_C)) TEST_O := $(patsubst %, %.o, $(TEST_GEN_PROGS)) TEST_O += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED)) -TEST_DEP_FILES := $(patsubst %.o, %.d, $(LIB_O)) +TEST_DEP_FILES := $(patsubst %.o, %.d, $(LIBLIVEUPDATE_O)) TEST_DEP_FILES += $(patsubst %.o, %.d, $(TEST_O)) -include $(TEST_DEP_FILES) -$(LIB_O): $(OUTPUT)/%.o: %.c - $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ - -$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: %.o $(LIB_O) - $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIB_O) $(LDLIBS) -o $@ +$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: %.o $(LIBLIVEUPDATE_O) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIBLIVEUPDATE_O) $(LDLIBS) -o $@ -EXTRA_CLEAN += $(LIB_O) EXTRA_CLEAN += $(TEST_O) EXTRA_CLEAN += $(TEST_DEP_FILES) diff --git a/tools/testing/selftests/liveupdate/config b/tools/testing/selftests/liveupdate/config index 91d03f9a6a39..016d009dba13 100644 --- a/tools/testing/selftests/liveupdate/config +++ b/tools/testing/selftests/liveupdate/config @@ -1,4 +1,5 @@ CONFIG_BLK_DEV_INITRD=y +CONFIG_DEVTMPFS=y CONFIG_KEXEC_FILE=y CONFIG_KEXEC_HANDOVER=y CONFIG_KEXEC_HANDOVER_ENABLE_DEFAULT=y diff --git a/tools/testing/selftests/liveupdate/config.aarch64 b/tools/testing/selftests/liveupdate/config.aarch64 new file mode 100644 index 000000000000..445716403925 --- /dev/null +++ b/tools/testing/selftests/liveupdate/config.aarch64 @@ -0,0 +1,2 @@ +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y diff --git a/tools/testing/selftests/liveupdate/config.x86_64 b/tools/testing/selftests/liveupdate/config.x86_64 new file mode 100644 index 000000000000..810d9c9d213e --- /dev/null +++ b/tools/testing/selftests/liveupdate/config.x86_64 @@ -0,0 +1,2 @@ +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y diff --git a/tools/testing/selftests/liveupdate/init.c b/tools/testing/selftests/liveupdate/init.c new file mode 100644 index 000000000000..fb08bd58b9b9 --- /dev/null +++ b/tools/testing/selftests/liveupdate/init.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin <pasha.tatashin@soleen.com> + */ +#include <fcntl.h> +#include <linux/kexec.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/reboot.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <unistd.h> + +#define COMMAND_LINE_SIZE 2048 +#define KERNEL_IMAGE "/kernel" +#define INITRD_IMAGE "/initrd.img" +#define TEST_BINARY "/test_binary" + +static int mount_filesystems(void) +{ + if (mount("devtmpfs", "/dev", "devtmpfs", 0, NULL) < 0) { + fprintf(stderr, "INIT: Warning: Failed to mount devtmpfs\n"); + return -1; + } + + if (mount("debugfs", "/debugfs", "debugfs", 0, NULL) < 0) { + fprintf(stderr, "INIT: Failed to mount debugfs\n"); + return -1; + } + + if (mount("proc", "/proc", "proc", 0, NULL) < 0) { + fprintf(stderr, "INIT: Failed to mount proc\n"); + return -1; + } + + return 0; +} + +static long kexec_file_load(int kernel_fd, int initrd_fd, + unsigned long cmdline_len, const char *cmdline, + unsigned long flags) +{ + return syscall(__NR_kexec_file_load, kernel_fd, initrd_fd, cmdline_len, + cmdline, flags); +} + +static int kexec_load(void) +{ + char cmdline[COMMAND_LINE_SIZE]; + int kernel_fd, initrd_fd, err; + ssize_t len; + int fd; + + fd = open("/proc/cmdline", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "INIT: Failed to read /proc/cmdline\n"); + + return -1; + } + + len = read(fd, cmdline, sizeof(cmdline) - 1); + close(fd); + if (len < 0) + return -1; + + cmdline[len] = 0; + if (len > 0 && cmdline[len - 1] == '\n') + cmdline[len - 1] = 0; + + strncat(cmdline, " luo_stage=2", sizeof(cmdline) - strlen(cmdline) - 1); + + kernel_fd = open(KERNEL_IMAGE, O_RDONLY); + if (kernel_fd < 0) { + fprintf(stderr, "INIT: Failed to open kernel image\n"); + return -1; + } + + initrd_fd = open(INITRD_IMAGE, O_RDONLY); + if (initrd_fd < 0) { + fprintf(stderr, "INIT: Failed to open initrd image\n"); + close(kernel_fd); + return -1; + } + + err = kexec_file_load(kernel_fd, initrd_fd, strlen(cmdline) + 1, + cmdline, 0); + + close(initrd_fd); + close(kernel_fd); + + return err; +} + +static int run_test(int stage) +{ + char stage_arg[32]; + int status; + pid_t pid; + + snprintf(stage_arg, sizeof(stage_arg), "%d", stage); + + pid = fork(); + if (pid < 0) + return -1; + + if (!pid) { + char *const argv[] = {TEST_BINARY, "-s", stage_arg, NULL}; + + execve(TEST_BINARY, argv, NULL); + fprintf(stderr, "INIT: execve failed\n"); + _exit(1); + } + + waitpid(pid, &status, 0); + + return (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : -1; +} + +static int get_current_stage(void) +{ + char cmdline[COMMAND_LINE_SIZE]; + ssize_t len; + int fd; + + fd = open("/proc/cmdline", O_RDONLY); + if (fd < 0) + return -1; + + len = read(fd, cmdline, sizeof(cmdline) - 1); + close(fd); + + if (len < 0) + return -1; + + cmdline[len] = 0; + + return strstr(cmdline, "luo_stage=2") ? 2 : 1; +} + +int main(int argc, char *argv[]) +{ + int current_stage; + int err; + + if (mount_filesystems()) + goto err_reboot; + + current_stage = get_current_stage(); + if (current_stage < 0) { + fprintf(stderr, "INIT: Failed to read cmdline"); + goto err_reboot; + } + + printf("INIT: Starting Stage %d\n", current_stage); + + if (current_stage == 1 && kexec_load()) { + fprintf(stderr, "INIT: Failed to load kexec kernel\n"); + goto err_reboot; + } + + if (run_test(current_stage)) { + fprintf(stderr, "INIT: Test binary returned failure\n"); + goto err_reboot; + } + + printf("INIT: Stage %d completed successfully.\n", current_stage); + reboot(current_stage == 1 ? RB_KEXEC : RB_AUTOBOOT); + + return 0; + +err_reboot: + reboot(RB_AUTOBOOT); + + return -1; +} diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h index 6a0d85386613..fa07fed08364 100644 --- a/tools/testing/selftests/liveupdate/luo_test_utils.h +++ b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h @@ -7,13 +7,13 @@ * Utility functions for LUO kselftests. */ -#ifndef LUO_TEST_UTILS_H -#define LUO_TEST_UTILS_H +#ifndef SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H +#define SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H #include <errno.h> #include <string.h> #include <linux/liveupdate.h> -#include "../kselftest.h" +#include "../../../kselftest.h" #define LUO_DEVICE "/dev/liveupdate" @@ -25,8 +25,11 @@ int luo_open_device(void); int luo_create_session(int luo_fd, const char *name); int luo_retrieve_session(int luo_fd, const char *name); int luo_session_finish(int session_fd); +int luo_get_session_name(int session_fd, char *name, size_t name_len); int luo_ensure_nofile_limit(long min_limit); +int luo_session_preserve_fd(int session_fd, int fd, __u64 token); +int luo_session_retrieve_fd(int session_fd, __u64 token); int create_and_preserve_memfd(int session_fd, int token, const char *data); int restore_and_verify_memfd(int session_fd, int token, const char *expected_data); @@ -43,4 +46,4 @@ typedef void (*luo_test_stage2_fn)(int luo_fd, int state_session_fd); int luo_test(int argc, char *argv[], const char *state_session_name, luo_test_stage1_fn stage1, luo_test_stage2_fn stage2); -#endif /* LUO_TEST_UTILS_H */ +#endif /* SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H */ diff --git a/tools/testing/selftests/liveupdate/lib/libliveupdate.mk b/tools/testing/selftests/liveupdate/lib/libliveupdate.mk new file mode 100644 index 000000000000..634cd4c16c47 --- /dev/null +++ b/tools/testing/selftests/liveupdate/lib/libliveupdate.mk @@ -0,0 +1,20 @@ +include $(top_srcdir)/scripts/subarch.include +ARCH ?= $(SUBARCH) + +LIBLIVEUPDATE_SRCDIR := $(selfdir)/liveupdate/lib + +LIBLIVEUPDATE_C := lu_utils.c + +LIBLIVEUPDATE_OUTPUT := $(OUTPUT)/libliveupdate + +LIBLIVEUPDATE_O := $(patsubst %.c, $(LIBLIVEUPDATE_OUTPUT)/%.o, $(LIBLIVEUPDATE_C)) + +CFLAGS += -I$(LIBLIVEUPDATE_SRCDIR)/include + +$(LIBLIVEUPDATE_OUTPUT): + $(Q)mkdir -p $@ + +$(LIBLIVEUPDATE_O): $(LIBLIVEUPDATE_OUTPUT)/%.o : $(LIBLIVEUPDATE_SRCDIR)/%.c | $(LIBLIVEUPDATE_OUTPUT) + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +EXTRA_CLEAN += $(LIBLIVEUPDATE_OUTPUT) diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/lib/lu_utils.c index 333a3530051b..74d41115c281 100644 --- a/tools/testing/selftests/liveupdate/luo_test_utils.c +++ b/tools/testing/selftests/liveupdate/lib/lu_utils.c @@ -21,8 +21,9 @@ #include <sys/stat.h> #include <errno.h> #include <stdarg.h> +#include <linux/unistd.h> -#include "luo_test_utils.h" +#include <libliveupdate.h> int luo_open_device(void) { @@ -59,7 +60,7 @@ int luo_create_session(int luo_fd, const char *name) snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s", LIVEUPDATE_SESSION_NAME_LENGTH - 1, name); - if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg) < 0) + if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg)) return -errno; return arg.fd; @@ -72,16 +73,58 @@ int luo_retrieve_session(int luo_fd, const char *name) snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s", LIVEUPDATE_SESSION_NAME_LENGTH - 1, name); - if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg) < 0) + if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg)) return -errno; return arg.fd; } +int luo_session_preserve_fd(int session_fd, int fd, __u64 token) +{ + struct liveupdate_session_preserve_fd arg = { + .size = sizeof(arg), + .fd = fd, + .token = token, + }; + + if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg)) + return -errno; + + return 0; +} + +int luo_session_retrieve_fd(int session_fd, __u64 token) +{ + struct liveupdate_session_retrieve_fd arg = { + .size = sizeof(arg), + .token = token, + }; + + if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg)) + return -errno; + + return arg.fd; +} + +/* Helper function to get a session name via ioctl. */ +int luo_get_session_name(int session_fd, char *name, size_t name_len) +{ + struct liveupdate_session_get_name args = {}; + + args.size = sizeof(args); + + if (ioctl(session_fd, LIVEUPDATE_SESSION_GET_NAME, &args)) + return -errno; + + strncpy(name, (char *)args.name, name_len - 1); + name[name_len - 1] = '\0'; + + return 0; +} + int create_and_preserve_memfd(int session_fd, int token, const char *data) { - struct liveupdate_session_preserve_fd arg = { .size = sizeof(arg) }; - long page_size = sysconf(_SC_PAGE_SIZE); + long page_size = getpagesize(); void *map = MAP_FAILED; int mfd = -1, ret = -1; @@ -99,9 +142,8 @@ int create_and_preserve_memfd(int session_fd, int token, const char *data) snprintf(map, page_size, "%s", data); munmap(map, page_size); - arg.fd = mfd; - arg.token = token; - if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg) < 0) + ret = luo_session_preserve_fd(session_fd, mfd, token); + if (ret) goto out; ret = 0; @@ -116,15 +158,13 @@ out: int restore_and_verify_memfd(int session_fd, int token, const char *expected_data) { - struct liveupdate_session_retrieve_fd arg = { .size = sizeof(arg) }; - long page_size = sysconf(_SC_PAGE_SIZE); + long page_size = getpagesize(); void *map = MAP_FAILED; int mfd = -1, ret = -1; - arg.token = token; - if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg) < 0) - return -errno; - mfd = arg.fd; + mfd = luo_session_retrieve_fd(session_fd, token); + if (mfd < 0) + return mfd; map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0); if (map == MAP_FAILED) @@ -228,16 +268,11 @@ void daemonize_and_wait(void) static int parse_stage_args(int argc, char *argv[]) { - static struct option long_options[] = { - {"stage", required_argument, 0, 's'}, - {0, 0, 0, 0} - }; - int option_index = 0; int stage = 1; int opt; optind = 1; - while ((opt = getopt_long(argc, argv, "s:", long_options, &option_index)) != -1) { + while ((opt = getopt(argc, argv, "s:")) != -1) { switch (opt) { case 's': stage = atoi(optarg); @@ -248,6 +283,7 @@ static int parse_stage_args(int argc, char *argv[]) fail_exit("Unknown argument"); } } + return stage; } @@ -275,7 +311,7 @@ int luo_test(int argc, char *argv[], fail_exit("Failed to check for state session"); if (target_stage != detected_stage) { - ksft_exit_fail_msg("Stage mismatch Requested --stage %d, but system is in stage %d.\n" + ksft_exit_fail_msg("Stage mismatch Requested stage %d, but system is in stage %d.\n" "(State session %s: %s)\n", target_stage, detected_stage, state_session_name, (detected_stage == 2) ? "EXISTS" : "MISSING"); diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c index 502fb3567e38..2dedd5fc2534 100644 --- a/tools/testing/selftests/liveupdate/liveupdate.c +++ b/tools/testing/selftests/liveupdate/liveupdate.c @@ -24,9 +24,9 @@ #include <sys/ioctl.h> #include <unistd.h> +#include <libliveupdate.h> #include <linux/liveupdate.h> -#include "luo_test_utils.h" #include "../kselftest.h" #include "../kselftest_harness.h" @@ -89,36 +89,6 @@ TEST_F(liveupdate_device, exclusive_open) EXPECT_EQ(errno, EBUSY); } -/* Helper function to create a LUO session via ioctl. */ -static int create_session(int lu_fd, const char *name) -{ - struct liveupdate_ioctl_create_session args = {}; - - args.size = sizeof(args); - strncpy((char *)args.name, name, sizeof(args.name) - 1); - - if (ioctl(lu_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &args)) - return -errno; - - return args.fd; -} - -/* Helper function to get a session name via ioctl. */ -static int get_session_name(int session_fd, char *name, size_t name_len) -{ - struct liveupdate_session_get_name args = {}; - - args.size = sizeof(args); - - if (ioctl(session_fd, LIVEUPDATE_SESSION_GET_NAME, &args)) - return -errno; - - strncpy(name, (char *)args.name, name_len - 1); - name[name_len - 1] = '\0'; - - return 0; -} - /* * Test Case: Create Duplicate Session * @@ -135,10 +105,10 @@ TEST_F(liveupdate_device, create_duplicate_session) ASSERT_GE(self->fd1, 0); - session_fd1 = create_session(self->fd1, "duplicate-session-test"); + session_fd1 = luo_create_session(self->fd1, "duplicate-session-test"); ASSERT_GE(session_fd1, 0); - session_fd2 = create_session(self->fd1, "duplicate-session-test"); + session_fd2 = luo_create_session(self->fd1, "duplicate-session-test"); EXPECT_LT(session_fd2, 0); EXPECT_EQ(-session_fd2, EEXIST); @@ -160,30 +130,16 @@ TEST_F(liveupdate_device, create_distinct_sessions) ASSERT_GE(self->fd1, 0); - session_fd1 = create_session(self->fd1, "distinct-session-1"); + session_fd1 = luo_create_session(self->fd1, "distinct-session-1"); ASSERT_GE(session_fd1, 0); - session_fd2 = create_session(self->fd1, "distinct-session-2"); + session_fd2 = luo_create_session(self->fd1, "distinct-session-2"); ASSERT_GE(session_fd2, 0); ASSERT_EQ(close(session_fd1), 0); ASSERT_EQ(close(session_fd2), 0); } -static int preserve_fd(int session_fd, int fd_to_preserve, __u64 token) -{ - struct liveupdate_session_preserve_fd args = {}; - - args.size = sizeof(args); - args.fd = fd_to_preserve; - args.token = token; - - if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &args)) - return -errno; - - return 0; -} - /* * Test Case: Preserve MemFD * @@ -201,14 +157,14 @@ TEST_F(liveupdate_device, preserve_memfd) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, "preserve-memfd-test"); + session_fd = luo_create_session(self->fd1, "preserve-memfd-test"); ASSERT_GE(session_fd, 0); mem_fd = memfd_create("test-memfd", 0); ASSERT_GE(mem_fd, 0); ASSERT_EQ(write(mem_fd, test_str, strlen(test_str)), strlen(test_str)); - ASSERT_EQ(preserve_fd(session_fd, mem_fd, 0x1234), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd, 0x1234), 0); ASSERT_EQ(close(session_fd), 0); ASSERT_EQ(lseek(mem_fd, 0, SEEK_SET), 0); @@ -236,7 +192,7 @@ TEST_F(liveupdate_device, preserve_multiple_memfds) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, "preserve-multi-memfd-test"); + session_fd = luo_create_session(self->fd1, "preserve-multi-memfd-test"); ASSERT_GE(session_fd, 0); mem_fd1 = memfd_create("test-memfd-1", 0); @@ -247,8 +203,8 @@ TEST_F(liveupdate_device, preserve_multiple_memfds) ASSERT_EQ(write(mem_fd1, test_str1, strlen(test_str1)), strlen(test_str1)); ASSERT_EQ(write(mem_fd2, test_str2, strlen(test_str2)), strlen(test_str2)); - ASSERT_EQ(preserve_fd(session_fd, mem_fd1, 0xAAAA), 0); - ASSERT_EQ(preserve_fd(session_fd, mem_fd2, 0xBBBB), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd1, 0xAAAA), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd2, 0xBBBB), 0); memset(read_buf, 0, sizeof(read_buf)); ASSERT_EQ(lseek(mem_fd1, 0, SEEK_SET), 0); @@ -284,9 +240,9 @@ TEST_F(liveupdate_device, preserve_complex_scenario) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd1 = create_session(self->fd1, "complex-session-1"); + session_fd1 = luo_create_session(self->fd1, "complex-session-1"); ASSERT_GE(session_fd1, 0); - session_fd2 = create_session(self->fd1, "complex-session-2"); + session_fd2 = luo_create_session(self->fd1, "complex-session-2"); ASSERT_GE(session_fd2, 0); mem_fd_data1 = memfd_create("data1", 0); @@ -303,10 +259,10 @@ TEST_F(liveupdate_device, preserve_complex_scenario) mem_fd_empty2 = memfd_create("empty2", 0); ASSERT_GE(mem_fd_empty2, 0); - ASSERT_EQ(preserve_fd(session_fd1, mem_fd_data1, 0x1111), 0); - ASSERT_EQ(preserve_fd(session_fd1, mem_fd_empty1, 0x2222), 0); - ASSERT_EQ(preserve_fd(session_fd2, mem_fd_data2, 0x3333), 0); - ASSERT_EQ(preserve_fd(session_fd2, mem_fd_empty2, 0x4444), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd_data1, 0x1111), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd_empty1, 0x2222), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd2, mem_fd_data2, 0x3333), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd2, mem_fd_empty2, 0x4444), 0); ASSERT_EQ(lseek(mem_fd_data1, 0, SEEK_SET), 0); ASSERT_EQ(read(mem_fd_data1, read_buf, sizeof(read_buf)), strlen(data1)); @@ -349,13 +305,13 @@ TEST_F(liveupdate_device, preserve_unsupported_fd) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, "unsupported-fd-test"); + session_fd = luo_create_session(self->fd1, "unsupported-fd-test"); ASSERT_GE(session_fd, 0); unsupported_fd = open("/dev/null", O_RDWR); ASSERT_GE(unsupported_fd, 0); - ret = preserve_fd(session_fd, unsupported_fd, 0xDEAD); + ret = luo_session_preserve_fd(session_fd, unsupported_fd, 0xDEAD); EXPECT_EQ(ret, -ENOENT); ASSERT_EQ(close(unsupported_fd), 0); @@ -379,23 +335,23 @@ TEST_F(liveupdate_device, prevent_double_preservation) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd1 = create_session(self->fd1, "double-preserve-session-1"); + session_fd1 = luo_create_session(self->fd1, "double-preserve-session-1"); ASSERT_GE(session_fd1, 0); - session_fd2 = create_session(self->fd1, "double-preserve-session-2"); + session_fd2 = luo_create_session(self->fd1, "double-preserve-session-2"); ASSERT_GE(session_fd2, 0); mem_fd = memfd_create("test-memfd", 0); ASSERT_GE(mem_fd, 0); /* First preservation should succeed */ - ASSERT_EQ(preserve_fd(session_fd1, mem_fd, 0x1111), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd, 0x1111), 0); /* Second preservation in a different session should fail with EBUSY */ - ret = preserve_fd(session_fd2, mem_fd, 0x2222); + ret = luo_session_preserve_fd(session_fd2, mem_fd, 0x2222); EXPECT_EQ(ret, -EBUSY); /* Second preservation in the same session (different token) should fail with EBUSY */ - ret = preserve_fd(session_fd1, mem_fd, 0x3333); + ret = luo_session_preserve_fd(session_fd1, mem_fd, 0x3333); EXPECT_EQ(ret, -EBUSY); ASSERT_EQ(close(mem_fd), 0); @@ -441,7 +397,7 @@ TEST_F(liveupdate_device, create_session_empty_name) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, ""); + session_fd = luo_create_session(self->fd1, ""); EXPECT_EQ(session_fd, -EINVAL); } @@ -462,10 +418,10 @@ TEST_F(liveupdate_device, get_session_name) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, session_name); + session_fd = luo_create_session(self->fd1, session_name); ASSERT_GE(session_fd, 0); - ASSERT_EQ(get_session_name(session_fd, name_buf, sizeof(name_buf)), 0); + ASSERT_EQ(luo_get_session_name(session_fd, name_buf, sizeof(name_buf)), 0); ASSERT_STREQ(name_buf, session_name); ASSERT_EQ(close(session_fd), 0); @@ -491,10 +447,10 @@ TEST_F(liveupdate_device, get_session_name_max_length) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, long_name); + session_fd = luo_create_session(self->fd1, long_name); ASSERT_GE(session_fd, 0); - ASSERT_EQ(get_session_name(session_fd, name_buf, sizeof(name_buf)), 0); + ASSERT_EQ(luo_get_session_name(session_fd, name_buf, sizeof(name_buf)), 0); ASSERT_STREQ(name_buf, long_name); ASSERT_EQ(close(session_fd), 0); @@ -528,7 +484,7 @@ TEST_F(liveupdate_device, preserve_many_sessions) char name[64]; snprintf(name, sizeof(name), "many-session-%d", i); - session_fds[i] = create_session(self->fd1, name); + session_fds[i] = luo_create_session(self->fd1, name); ASSERT_GE(session_fds[i], 0); } @@ -554,7 +510,7 @@ TEST_F(liveupdate_device, preserve_many_files) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, "many-files-test"); + session_fd = luo_create_session(self->fd1, "many-files-test"); ASSERT_GE(session_fd, 0); ret = luo_ensure_nofile_limit(MANY_FILES + 10); @@ -565,7 +521,7 @@ TEST_F(liveupdate_device, preserve_many_files) for (i = 0; i < MANY_FILES; i++) { mem_fds[i] = memfd_create("test-memfd", 0); ASSERT_GE(mem_fds[i], 0); - ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fds[i], i), 0); } for (i = 0; i < MANY_FILES; i++) diff --git a/tools/testing/selftests/liveupdate/luo_kexec_simple.c b/tools/testing/selftests/liveupdate/luo_kexec_simple.c index d7ac1f3dc4cb..786ac93b9ae3 100644 --- a/tools/testing/selftests/liveupdate/luo_kexec_simple.c +++ b/tools/testing/selftests/liveupdate/luo_kexec_simple.c @@ -8,7 +8,7 @@ * across a single kexec reboot. */ -#include "luo_test_utils.h" +#include <libliveupdate.h> #define TEST_SESSION_NAME "test-session" #define TEST_MEMFD_TOKEN 0x1A diff --git a/tools/testing/selftests/liveupdate/luo_multi_session.c b/tools/testing/selftests/liveupdate/luo_multi_session.c index 0ee2d795beef..aac24a5f5ce3 100644 --- a/tools/testing/selftests/liveupdate/luo_multi_session.c +++ b/tools/testing/selftests/liveupdate/luo_multi_session.c @@ -9,7 +9,7 @@ * files. */ -#include "luo_test_utils.h" +#include <libliveupdate.h> #define SESSION_EMPTY_1 "multi-test-empty-1" #define SESSION_EMPTY_2 "multi-test-empty-2" diff --git a/tools/testing/selftests/liveupdate/luo_stress_files.c b/tools/testing/selftests/liveupdate/luo_stress_files.c index 0cdf9cd4bac7..a0d48490f4ed 100644 --- a/tools/testing/selftests/liveupdate/luo_stress_files.c +++ b/tools/testing/selftests/liveupdate/luo_stress_files.c @@ -10,7 +10,8 @@ #include <stdio.h> #include <unistd.h> -#include "luo_test_utils.h" + +#include <libliveupdate.h> #define NUM_FILES 500 #define STATE_SESSION_NAME "kexec_many_files_state" diff --git a/tools/testing/selftests/liveupdate/luo_stress_sessions.c b/tools/testing/selftests/liveupdate/luo_stress_sessions.c index f201b1839d1d..278aebabe0e9 100644 --- a/tools/testing/selftests/liveupdate/luo_stress_sessions.c +++ b/tools/testing/selftests/liveupdate/luo_stress_sessions.c @@ -10,7 +10,8 @@ #include <stdio.h> #include <unistd.h> -#include "luo_test_utils.h" + +#include <libliveupdate.h> #define NUM_SESSIONS 2000 #define STATE_SESSION_NAME "kexec_many_state" diff --git a/tools/testing/selftests/liveupdate/run-vmtests.sh b/tools/testing/selftests/liveupdate/run-vmtests.sh new file mode 100755 index 000000000000..d656ce58c5a7 --- /dev/null +++ b/tools/testing/selftests/liveupdate/run-vmtests.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +SCRIPT_DIR=$(dirname "$(realpath "$0")") +TEST_RUNNER="$SCRIPT_DIR/vmtest.sh" + +TARGETS=("x86_64" "aarch64") + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + +passed=0 +failed=0 +skipped=0 + +TEST_NAMES=( + "luo_kexec_simple" + "luo_multi_session" + "luo_stress_files" + "luo_stress_sessions" +) + +function usage() { + cat <<EOF +$0 [-k] [-o output_dir] [-h] +Options: + -k) keep logs + -o) specify output directory + -h) display this help +EOF +} + +function pass() { + echo -e "${GREEN}PASS${NC}" + ((passed++)) +} + +function skip() { + echo -e "${YELLOW}SKIP${NC}" + ((skipped++)) +} + +function fail() { + echo -e "${RED}FAIL${NC}" + ((failed++)) +} + +function main() { + while getopts 'hko:' opt; do + case $opt in + k) keep_logs=1 ;; + o) output_dir=$OPTARG ;; + h) usage; exit 0 ;; + *) echo Unknown argument "$opt" + usage; exit 1 ;; + esac + done + + if [ -n "$keep_logs" ]; then + if [ -z "$output_dir" ]; then + output_dir="$SCRIPT_DIR/results_$(date +%Y%m%d_%H%M%S)" + fi; + mkdir -p "$output_dir" + else + output_dir=$(mktemp -d /tmp/luo.XXXXXXXX) + trap 'rm -fr "$output_dir"' EXIT + fi + + for arch in "${TARGETS[@]}"; do + for test_name in "${TEST_NAMES[@]}"; do + log="$output_dir/${arch}_${test_name}.log" + + printf " -> %-8s %-24s ... " "$arch" "$test_name" + + "$TEST_RUNNER" -t "$arch" -T "$test_name" &> "$log" + exit_code=$? + + case $exit_code in + 0) pass;; + 4) skip;; + *) fail;; + esac + done + echo "" + done + + echo "SUMMARY: PASS=$passed SKIP=$skipped FAIL=$failed" + if [ -n "$keep_logs" ]; then + echo "Logs: $output_dir" + fi + + exit $((failed != 0)) +} + +main "$@" diff --git a/tools/testing/selftests/liveupdate/vmtest.sh b/tools/testing/selftests/liveupdate/vmtest.sh new file mode 100755 index 000000000000..64fd2ab55b76 --- /dev/null +++ b/tools/testing/selftests/liveupdate/vmtest.sh @@ -0,0 +1,263 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +set -ue + +CROSS_COMPILE="${CROSS_COMPILE:-""}" + +test_dir=$(realpath "$(dirname "$0")") +kernel_dir=$(realpath "$test_dir/../../../..") + +workspace_dir="" +headers_dir="" +initrd="" +KEEP_WORKSPACE=0 + +source "$test_dir/../kselftest/ktap_helpers.sh" + +function get_arch_conf() { + local arch=$1 + if [[ "$arch" == "arm64" ]]; then + QEMU_CMD="qemu-system-aarch64 -M virt -cpu max" + KERNEL_IMAGE="Image" + KERNEL_CMDLINE="console=ttyAMA0" + elif [[ "$arch" == "x86" ]]; then + QEMU_CMD="qemu-system-x86_64" + KERNEL_IMAGE="bzImage" + KERNEL_CMDLINE="console=ttyS0" + else + echo "Unsupported architecture: $arch" + exit 1 + fi +} + +function usage() { + cat <<EOF +$0 [-d build_dir] [-j jobs] [-t target_arch] [-T test_name] [-w workspace_dir] [-k] [-h] +Options: + -d) path to the kernel build directory (default: .luo_test_build.<arch>) + -j) number of jobs for compilation + -t) run test for target_arch (aarch64, x86_64) + -T) test name to run (default: luo_kexec_simple) + -w) custom workspace directory (default: creates temp dir) + -k) keep workspace directory after successful test + -h) display this help +EOF +} + +function cleanup() { + if [ "$KEEP_WORKSPACE" -eq 1 ]; then + echo "# Workspace preserved at: $workspace_dir" + else + rm -fr "$workspace_dir" + fi + + ktap_finished +} + +function skip() { + local msg=${1:-""} + ktap_test_skip "$msg" + exit "$KSFT_SKIP" +} + +function fail() { + local msg=${1:-""} + ktap_test_fail "$msg" + exit "$KSFT_FAIL" +} + +function detect_cross_compile() { + local target=$1 + local host=$(uname -m) + + [[ "$host" == "arm64" ]] && host="aarch64" + [[ "$target" == "arm64" ]] && target="aarch64" + + if [[ "$host" == "$target" ]]; then + CROSS_COMPILE="" + return + fi + + if [[ -n "$CROSS_COMPILE" ]]; then + return + fi + + local candidate="" + case "$target" in + aarch64) candidate="aarch64-linux-gnu-" ;; + x86_64) candidate="x86_64-linux-gnu-" ;; + *) skip "Auto-detection for target '$target' not supported. Please set CROSS_COMPILE manually." ;; + esac + + if command -v "${candidate}gcc" &> /dev/null; then + CROSS_COMPILE="$candidate" + else + skip "Compiler '${candidate}gcc' not found. Please install it (e.g., 'apt install gcc-aarch64-linux-gnu') or set CROSS_COMPILE." + fi +} + +function build_kernel() { + local build_dir=$1 + local make_cmd=$2 + local kimage=$3 + local target_arch=$4 + + local luo_config="$build_dir/luo.config" + local kconfig="$build_dir/.config" + local common_conf="$test_dir/config" + local arch_conf="$test_dir/config.$target_arch" + + echo "# Building kernel in: $build_dir" + + cat "$arch_conf" "$common_conf" | tee "$kconfig" > "$luo_config" + $make_cmd olddefconfig + + # verify that kernel confiration has all necessary options + while read -r opt ; do + grep "$opt" "$kconfig" &>/dev/null || skip "$opt is missing" + done < "$luo_config" + + $make_cmd "$kimage" + $make_cmd headers_install INSTALL_HDR_PATH="$headers_dir" +} + +function mkinitrd() { + local build_dir=$1 + local kernel_path=$2 + local test_name=$3 + + # Compile the test binary and the init process + "$CROSS_COMPILE"gcc -static -O2 -nostdinc -nostdlib \ + -I "$headers_dir/include" \ + -I "$kernel_dir/tools/include/nolibc" \ + -I "$test_dir/lib/include" \ + -o "$workspace_dir/test_binary" \ + "$test_dir/$test_name.c" "$test_dir/lib/lu_utils.c" + + "$CROSS_COMPILE"gcc -s -static -Os -nostdinc -nostdlib \ + -fno-asynchronous-unwind-tables -fno-ident \ + -fno-stack-protector \ + -I "$headers_dir/include" \ + -I "$kernel_dir/tools/include/nolibc" \ + -o "$workspace_dir/init" "$test_dir/init.c" + + cat > "$workspace_dir/cpio_list_inner" <<EOF +dir /dev 0755 0 0 +dir /proc 0755 0 0 +dir /debugfs 0755 0 0 +nod /dev/console 0600 0 0 c 5 1 +file /init $workspace_dir/init 0755 0 0 +file /test_binary $workspace_dir/test_binary 0755 0 0 +EOF + + # Generate inner_initrd.cpio + "$build_dir/usr/gen_init_cpio" "$workspace_dir/cpio_list_inner" > "$workspace_dir/inner_initrd.cpio" + + cat > "$workspace_dir/cpio_list" <<EOF +dir /dev 0755 0 0 +dir /proc 0755 0 0 +dir /debugfs 0755 0 0 +nod /dev/console 0600 0 0 c 5 1 +file /init $workspace_dir/init 0755 0 0 +file /kernel $kernel_path 0644 0 0 +file /test_binary $workspace_dir/test_binary 0755 0 0 +file /initrd.img $workspace_dir/inner_initrd.cpio 0644 0 0 +EOF + + # Generate the final initrd + "$build_dir/usr/gen_init_cpio" "$workspace_dir/cpio_list" > "$initrd" +} + +function run_qemu() { + local qemu_cmd=$1 + local cmdline=$2 + local kernel_path=$3 + local serial="$workspace_dir/qemu.serial" + + cmdline="$cmdline liveupdate=on panic=-1" + + echo "# Serial Log: $serial" + timeout 30s \ + $qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \ + -accel tcg -accel hvf -accel kvm \ + -serial file:"$serial" \ + -append "$cmdline" \ + -kernel "$kernel_path" \ + -initrd "$initrd" + + grep "TEST PASSED" "$serial" &> /dev/null || fail "Liveupdate failed" +} + +function target_to_arch() { + local target=$1 + case $target in + aarch64) echo "arm64" ;; + x86_64) echo "x86" ;; + *) skip "architecture $target is not supported" + esac +} + +function main() { + local build_dir="" + local jobs=$(nproc) + local target="$(uname -m)" + local test_name="luo_kexec_simple" + local workspace_arg="" + + set -o errtrace + trap fail ERR + + while getopts 'hd:j:t:T:w:k' opt; do + case $opt in + d) build_dir="$OPTARG" ;; + j) jobs="$OPTARG" ;; + t) target="$OPTARG" ;; + T) test_name="$OPTARG" ;; + w) workspace_arg="$OPTARG" ;; + k) KEEP_WORKSPACE=1 ;; + h) usage; exit 0 ;; + *) echo "Unknown argument $opt"; usage; exit 1 ;; + esac + done + + ktap_print_header + ktap_set_plan 1 + trap cleanup EXIT + + if [ -n "$workspace_arg" ]; then + workspace_dir="$(realpath -m "$workspace_arg")" + mkdir -p "$workspace_dir" + else + workspace_dir=$(mktemp -d /tmp/luo-test.XXXXXXXX) + fi + + echo "# Workspace created at: $workspace_dir" + headers_dir="$workspace_dir/usr" + initrd="$workspace_dir/initrd.cpio" + + detect_cross_compile "$target" + + local arch=$(target_to_arch "$target") + + if [ -z "$build_dir" ]; then + build_dir="$kernel_dir/.luo_test_build.$arch" + fi + + mkdir -p "$build_dir" + build_dir=$(realpath "$build_dir") + get_arch_conf "$arch" + + local make_cmd="make -s ARCH=$arch CROSS_COMPILE=$CROSS_COMPILE -j$jobs" + local make_cmd_build="$make_cmd -C $kernel_dir O=$build_dir" + + build_kernel "$build_dir" "$make_cmd_build" "$KERNEL_IMAGE" "$target" + + local final_kernel="$build_dir/arch/$arch/boot/$KERNEL_IMAGE" + mkinitrd "$build_dir" "$final_kernel" "$test_name" + + run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$final_kernel" + ktap_test_pass "$test_name succeeded" +} + +main "$@" diff --git a/tools/testing/selftests/lkdtm/tests.txt b/tools/testing/selftests/lkdtm/tests.txt index d8180bbe31e8..bec57a02913a 100644 --- a/tools/testing/selftests/lkdtm/tests.txt +++ b/tools/testing/selftests/lkdtm/tests.txt @@ -78,7 +78,7 @@ USERCOPY_STACK_FRAME_TO USERCOPY_STACK_FRAME_FROM USERCOPY_STACK_BEYOND USERCOPY_KERNEL -STACKLEAK_ERASING OK: the rest of the thread stack is properly erased +KSTACK_ERASE OK: the rest of the thread stack is properly erased CFI_FORWARD_PROTO CFI_BACKWARD call trace:|ok: control flow unchanged FORTIFY_STRSCPY detected buffer overflow diff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c index e4c49699f3f7..2f2b9879d100 100644 --- a/tools/testing/selftests/mm/hmm-tests.c +++ b/tools/testing/selftests/mm/hmm-tests.c @@ -23,6 +23,7 @@ #include <time.h> #include <pthread.h> #include <limits.h> +#include <linux/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> diff --git a/tools/testing/selftests/mm/ksft_process_madv.sh b/tools/testing/selftests/mm/ksft_process_madv.sh index 2c3137ae8bc8..edad2d2d888f 100755 --- a/tools/testing/selftests/mm/ksft_process_madv.sh +++ b/tools/testing/selftests/mm/ksft_process_madv.sh @@ -1,4 +1,4 @@ #!/bin/sh -e # SPDX-License-Identifier: GPL-2.0 -./run_vmtests.sh -t mmap +./run_vmtests.sh -t process_madv diff --git a/tools/testing/selftests/mm/mlock-random-test.c b/tools/testing/selftests/mm/mlock-random-test.c index 9d349c151360..16294bc7dae6 100644 --- a/tools/testing/selftests/mm/mlock-random-test.c +++ b/tools/testing/selftests/mm/mlock-random-test.c @@ -84,7 +84,7 @@ int get_proc_locked_vm_size(void) int get_proc_page_size(unsigned long addr) { FILE *smaps; - char *line; + char *line = NULL; unsigned long mmupage_size = 0; size_t size; diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c index 762306177ad8..f9bcff8e78fa 100644 --- a/tools/testing/selftests/mm/pagemap_ioctl.c +++ b/tools/testing/selftests/mm/pagemap_ioctl.c @@ -1051,6 +1051,57 @@ static void test_simple(void) ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__); } +/* + * A range that was populated and then MADV_DONTNEED'd is genuine pte_none + * with no uffd-wp marker. Such a pte must read the same regardless of which + * PAGEMAP_SCAN path serves the request: both the PAGE_IS_WRITTEN fast path and + * the generic path (reached e.g. via category_anyof_mask) must report every + * page written. + */ +static void unpopulated_scan_test(void) +{ + int npages = 16, i; + long mem_size = npages * page_size; + struct page_region regions[16]; + long fast = 0, slow = 0, ret; + char *mem; + + mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mem == MAP_FAILED) + ksft_exit_fail_msg("%s mmap failed\n", __func__); + + wp_init(mem, mem_size); + + /* Populate, then drop: the ptes become pte_none without a marker. */ + memset(mem, 1, mem_size); + if (madvise(mem, mem_size, MADV_DONTNEED)) + ksft_exit_fail_msg("%s MADV_DONTNEED failed\n", __func__); + + /* Fast path: category_mask == return_mask == PAGE_IS_WRITTEN. */ + ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0, + PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN); + if (ret < 0) + ksft_exit_fail_msg("%s fast scan failed\n", __func__); + for (i = 0; i < ret; i++) + fast += LEN(regions[i]); + + /* Generic path: same query expressed via category_anyof_mask. */ + ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0, + 0, PAGE_IS_WRITTEN, 0, PAGE_IS_WRITTEN); + if (ret < 0) + ksft_exit_fail_msg("%s generic scan failed\n", __func__); + for (i = 0; i < ret; i++) + slow += LEN(regions[i]); + + ksft_test_result(fast == npages && slow == npages, + "%s unpopulated ptes reported written by both paths (%ld, %ld of %d)\n", + __func__, fast, slow, npages); + + wp_free(mem, mem_size); + munmap(mem, mem_size); +} + int sanity_tests(void) { unsigned long long mem_size, vec_size; @@ -1368,7 +1419,7 @@ void *thread_proc(void *mem) ksft_exit_fail_msg("pthread_barrier_wait\n"); for (i = 0; i < access_per_thread; ++i) - __atomic_add_fetch(m + i * (0x1000 / sizeof(*m)), 1, __ATOMIC_SEQ_CST); + __atomic_add_fetch(m + i * (page_size / sizeof(*m)), 1, __ATOMIC_SEQ_CST); ret = pthread_barrier_wait(&end_barrier); if (ret && ret != PTHREAD_BARRIER_SERIAL_THREAD) @@ -1403,15 +1454,15 @@ static void transact_test(int page_size) if (pthread_barrier_init(&end_barrier, NULL, nthreads + 1)) ksft_exit_fail_msg("pthread_barrier_init\n"); - mem = mmap(NULL, 0x1000 * nthreads * pages_per_thread, PROT_READ | PROT_WRITE, + mem = mmap(NULL, page_size * nthreads * pages_per_thread, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (mem == MAP_FAILED) ksft_exit_fail_msg("Error mmap %s.\n", strerror(errno)); - wp_init(mem, 0x1000 * nthreads * pages_per_thread); - wp_addr_range(mem, 0x1000 * nthreads * pages_per_thread); + wp_init(mem, page_size * nthreads * pages_per_thread); + wp_addr_range(mem, page_size * nthreads * pages_per_thread); - memset(mem, 0, 0x1000 * nthreads * pages_per_thread); + memset(mem, 0, page_size * nthreads * pages_per_thread); count = get_dirty_pages_reset(mem, nthreads * pages_per_thread, 1, page_size); ksft_test_result(count > 0, "%s count %u\n", __func__, count); @@ -1420,7 +1471,7 @@ static void transact_test(int page_size) finish = 0; for (i = 0; i < nthreads; ++i) - pthread_create(&th, NULL, thread_proc, mem + 0x1000 * i * pages_per_thread); + pthread_create(&th, NULL, thread_proc, mem + page_size * i * pages_per_thread); extra_pages = 0; for (i = 0; i < iter_count; ++i) { @@ -1559,7 +1610,7 @@ int main(int __attribute__((unused)) argc, char *argv[]) if (!hugetlb_setup_default(4)) ksft_print_msg("HugeTLB test will be skipped\n"); - ksft_set_plan(117); + ksft_set_plan(118); page_size = getpagesize(); hpage_size = read_pmd_pagesize(); @@ -1737,6 +1788,9 @@ int main(int __attribute__((unused)) argc, char *argv[]) /* 17. ZEROPFN tests */ zeropfn_tests(); + /* 18. Unpopulated pte scan-path consistency */ + unpopulated_scan_test(); + close(pagemap_fd); ksft_finished(); } diff --git a/tools/testing/selftests/namespaces/nsid_test.c b/tools/testing/selftests/namespaces/nsid_test.c index 46dc838cba82..a16f31f41d38 100644 --- a/tools/testing/selftests/namespaces/nsid_test.c +++ b/tools/testing/selftests/namespaces/nsid_test.c @@ -649,8 +649,6 @@ TEST_F(nsid, timens_separate) /* Fork a grandchild to actually enter the new namespace */ pid_t grandchild = fork(); if (grandchild == 0) { - /* Grandchild is in the new namespace */ - write(pipefd[1], "Y", 1); close(pipefd[1]); pause(); _exit(0); @@ -771,8 +769,6 @@ TEST_F(nsid, pidns_separate) /* Fork a grandchild to actually enter the new namespace */ pid_t grandchild = fork(); if (grandchild == 0) { - /* Grandchild is in the new namespace */ - write(pipefd[1], "Y", 1); close(pipefd[1]); pause(); _exit(0); diff --git a/tools/testing/selftests/net/af_unix/.gitignore b/tools/testing/selftests/net/af_unix/.gitignore index 240b26740c9e..973176644103 100644 --- a/tools/testing/selftests/net/af_unix/.gitignore +++ b/tools/testing/selftests/net/af_unix/.gitignore @@ -6,3 +6,4 @@ scm_rights so_peek_off unix_connect unix_connreset +unix_listen diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile index 4c0375e28bbe..57d159803a3a 100644 --- a/tools/testing/selftests/net/af_unix/Makefile +++ b/tools/testing/selftests/net/af_unix/Makefile @@ -14,6 +14,7 @@ TEST_GEN_PROGS := \ so_peek_off \ unix_connect \ unix_connreset \ + unix_listen \ # end of TEST_GEN_PROGS include ../../lib.mk diff --git a/tools/testing/selftests/net/af_unix/config b/tools/testing/selftests/net/af_unix/config index b5429c15a53c..41dbb03c747e 100644 --- a/tools/testing/selftests/net/af_unix/config +++ b/tools/testing/selftests/net/af_unix/config @@ -1,3 +1,4 @@ CONFIG_AF_UNIX_OOB=y CONFIG_UNIX=y CONFIG_UNIX_DIAG=m +CONFIG_USER_NS=y diff --git a/tools/testing/selftests/net/af_unix/unix_listen.c b/tools/testing/selftests/net/af_unix/unix_listen.c new file mode 100644 index 000000000000..416fa3e5bfe9 --- /dev/null +++ b/tools/testing/selftests/net/af_unix/unix_listen.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tests for the state checks in AF_UNIX listen(). + * + * The central case is a regression test: listen() on a bound socket that + * is already connected (i.e. not in TCP_CLOSE or TCP_LISTEN state) must + * fail with EINVAL. A prior change accidentally let it return success + * without doing anything, because a helper called in between reset the + * error code to 0. The neighbouring checks (unbound, already listening) + * are tested too so they cannot silently regress the same way. + * + * Every case runs for both listenable socket types (SOCK_STREAM and + * SOCK_SEQPACKET) and both pathname and abstract addresses. + */ +#define _GNU_SOURCE + +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include "kselftest_harness.h" + +#define SK_NAME "unix_listen_sk" +#define SRV_NAME "unix_listen_srv" + +FIXTURE(unix_listen) +{ + int sk; /* socket under test */ + int server; /* a listening peer, when a test needs one */ + struct sockaddr_un addr, srv_addr; + socklen_t addrlen, srv_addrlen; +}; + +FIXTURE_VARIANT(unix_listen) +{ + int type; + int abstract; +}; + +FIXTURE_VARIANT_ADD(unix_listen, stream_pathname) +{ + .type = SOCK_STREAM, + .abstract = 0, +}; + +FIXTURE_VARIANT_ADD(unix_listen, stream_abstract) +{ + .type = SOCK_STREAM, + .abstract = 1, +}; + +FIXTURE_VARIANT_ADD(unix_listen, seqpacket_pathname) +{ + .type = SOCK_SEQPACKET, + .abstract = 0, +}; + +FIXTURE_VARIANT_ADD(unix_listen, seqpacket_abstract) +{ + .type = SOCK_SEQPACKET, + .abstract = 1, +}; + +/* Fill @addr with a pathname or abstract address named @name. */ +static socklen_t unix_set_addr(struct sockaddr_un *addr, const char *name, + int abstract) +{ + size_t len = strlen(name); + + memset(addr, 0, sizeof(*addr)); + addr->sun_family = AF_UNIX; + /* An abstract address leads with a NUL and has no filesystem entry. */ + memcpy(addr->sun_path + (abstract ? 1 : 0), name, len); + + return offsetof(struct sockaddr_un, sun_path) + len + 1; +} + +FIXTURE_SETUP(unix_listen) +{ + self->sk = -1; + self->server = -1; + self->addrlen = unix_set_addr(&self->addr, SK_NAME, variant->abstract); + self->srv_addrlen = unix_set_addr(&self->srv_addr, SRV_NAME, + variant->abstract); +} + +FIXTURE_TEARDOWN(unix_listen) +{ + if (self->sk >= 0) + close(self->sk); + if (self->server >= 0) + close(self->server); + + /* Pathname sockets leave a filesystem entry behind; abstract ones do not. */ + if (!variant->abstract) { + remove(SK_NAME); + remove(SRV_NAME); + } +} + +/* A bound socket in TCP_CLOSE is the normal, allowed case. */ +TEST_F(unix_listen, bound_is_ok) +{ + int err; + + self->sk = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->sk); + + err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen); + ASSERT_EQ(0, err); + + err = listen(self->sk, 8); + EXPECT_EQ(0, err); +} + +/* Listening again on an already-listening socket (TCP_LISTEN) is allowed. */ +TEST_F(unix_listen, relisten_is_ok) +{ + int err; + + self->sk = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->sk); + + err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen); + ASSERT_EQ(0, err); + + err = listen(self->sk, 8); + ASSERT_EQ(0, err); + + err = listen(self->sk, 16); + EXPECT_EQ(0, err); +} + +/* listen() on an unbound socket fails: there is nothing to listen on. */ +TEST_F(unix_listen, unbound_is_einval) +{ + int err; + + self->sk = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->sk); + + err = listen(self->sk, 8); + EXPECT_EQ(-1, err); + EXPECT_EQ(EINVAL, errno); +} + +/* + * The regression: a bound socket that has already been connected is not in + * TCP_CLOSE or TCP_LISTEN, so listen() must reject it with EINVAL rather + * than quietly succeeding. + */ +TEST_F(unix_listen, connected_is_einval) +{ + int err; + + self->server = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->server); + + err = bind(self->server, (struct sockaddr *)&self->srv_addr, + self->srv_addrlen); + ASSERT_EQ(0, err); + + err = listen(self->server, 8); + ASSERT_EQ(0, err); + + self->sk = socket(AF_UNIX, variant->type, 0); + ASSERT_LE(0, self->sk); + + /* Bind first so the unbound check does not mask the state check. */ + err = bind(self->sk, (struct sockaddr *)&self->addr, self->addrlen); + ASSERT_EQ(0, err); + + err = connect(self->sk, (struct sockaddr *)&self->srv_addr, + self->srv_addrlen); + ASSERT_EQ(0, err); + + err = listen(self->sk, 8); + EXPECT_EQ(-1, err); + EXPECT_EQ(EINVAL, errno); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/net/bridge_vlan_dump.sh b/tools/testing/selftests/net/bridge_vlan_dump.sh index ad66731d2a6f..90e18e2104e3 100755 --- a/tools/testing/selftests/net/bridge_vlan_dump.sh +++ b/tools/testing/selftests/net/bridge_vlan_dump.sh @@ -13,6 +13,7 @@ ALL_TESTS=" vlan_range_mcast_max_groups vlan_range_mcast_n_groups vlan_range_mcast_enabled + vlan_range_pvid " setup_prepare() @@ -191,6 +192,28 @@ vlan_range_mcast_enabled() log_test "VLAN range grouping with mcast_enabled" } +vlan_range_pvid() +{ + RET=0 + + ip -n "$NS" link set dev br0 type bridge vlan_default_pvid 1 + check_err $? "Failed to configure default PVID" + defer ip -n "$NS" link set dev br0 type bridge vlan_default_pvid 0 + + bridge -n "$NS" vlan add vid 2 dev dummy0 untagged + check_err $? "Failed to add VLAN 2" + defer bridge -n "$NS" vlan del vid 2 dev dummy0 + + bridge -n "$NS" -d vlan show dev dummy0 | + grep -Eq '(^|[[:space:]])2([[:space:]]|$)' + check_err $? "VLAN following PVID is missing from detailed dump" + + bridge -n "$NS" -d vlan show dev dummy0 | grep -q "1-2" + check_fail $? "PVID was incorrectly included in a VLAN range" + + log_test "PVID is isolated from VLAN dump ranges" +} + # Verify the newest tested option is supported if ! bridge vlan help 2>&1 | grep -q "neigh_suppress"; then echo "SKIP: iproute2 too old, missing per-VLAN neighbor suppression support" diff --git a/tools/testing/selftests/net/lib.sh b/tools/testing/selftests/net/lib.sh index b3827b43782b..d46d2cec89e4 100644 --- a/tools/testing/selftests/net/lib.sh +++ b/tools/testing/selftests/net/lib.sh @@ -70,12 +70,33 @@ ksft_exit_status_merge() $ksft_xfail $ksft_pass $ksft_skip $ksft_fail } +timestamp_ms() +{ + local now + local seconds + local nanoseconds + + now=$(date -u +%s:%N) || return + seconds=${now%:*} + nanoseconds=${now#*:} + + if [[ $nanoseconds =~ ^[0-9]+$ ]]; then + nanoseconds=${nanoseconds:0:9} + else + nanoseconds=0 + fi + + echo $((seconds * 1000 + 10#$nanoseconds / 1000000)) +} + loopy_wait() { local sleep_cmd=$1; shift local timeout_ms=$1; shift + local start_time + local current_time - local start_time="$(date -u +%s%3N)" + start_time=$(timestamp_ms) || return while true do local out @@ -84,7 +105,7 @@ loopy_wait() return 0 fi - local current_time="$(date -u +%s%3N)" + current_time=$(timestamp_ms) || return if ((current_time - start_time > timeout_ms)); then echo -n "$out" return 1 diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/selftests/net/lib/py/utils.py index 87eae79d01c1..184bb04343f6 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -44,7 +44,7 @@ class cmd: Use bkg() instead to run a command in the background. """ def __init__(self, comm, shell=None, fail=True, expect_fail=False, ns=None, - background=False, host=None, timeout=5, ksft_ready=None, + background=False, host=None, timeout=20, ksft_ready=None, ksft_wait=None): if ns: if hasattr(ns, 'user_ns_path'): @@ -113,7 +113,7 @@ class cmd: return stdout, stderr - def process(self, terminate=True, fail=None, expect_fail=False, timeout=5): + def process(self, terminate=True, fail=None, expect_fail=False, timeout=20): if fail is None: fail = not terminate diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh index c0aeffd5cb71..7dc91fac4917 100755 --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh @@ -584,7 +584,7 @@ check_transfer() mv "$tmpfile" "$out" tmpfile="" fi - cmp -l "$in" "$out" | while read -r i a b; do + while read -r i a b; do local sum=$((0${a} + 0${b})) if [ $check_invert -eq 0 ] || [ $sum -ne $((0xff)) ]; then fail_test "$what does not match (in, out):" @@ -595,7 +595,7 @@ check_transfer() else print_info "$what has inverted byte at ${i}" fi - done + done < <(cmp -l "$in" "$out") return 0 } diff --git a/tools/testing/selftests/net/mptcp/userspace_pm.sh b/tools/testing/selftests/net/mptcp/userspace_pm.sh index e9ae1806ab07..30a809752d1b 100755 --- a/tools/testing/selftests/net/mptcp/userspace_pm.sh +++ b/tools/testing/selftests/net/mptcp/userspace_pm.sh @@ -212,7 +212,7 @@ make_connection() ./mptcp_connect -s MPTCP -w 300 -p $app_port -l $listen_addr > /dev/null 2>&1 & local server_pid=$! - mptcp_lib_wait_local_port_listen "${ns1}" "${port}" + mptcp_lib_wait_local_port_listen "${ns1}" "${app_port}" # Run the client, transfer $file and stay connected to the server # to conduct tests diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh index 08ad07500e8a..449c518bd947 100755 --- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh +++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh @@ -617,7 +617,11 @@ ip -6 -net "$nsr2" route add default via fee1:3::1 ip -net "$ns2" route add default via 10.0.2.1 ip -6 -net "$ns2" route add default via dead:2::1 +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif tun0 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0 accept' +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif tun6 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6 accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward \ 'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept' @@ -629,7 +633,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel"; then fi if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then - echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel" + check_counters "flow offload for ns1/ns2 IP6IP6 tunnel" else echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel" 1>&2 ip netns exec "$nsr1" nft list ruleset @@ -642,6 +646,8 @@ ip -net "$nsr1" link set veth1.10 up ip -net "$nsr1" addr add 192.168.20.1/24 dev veth1.10 ip -net "$nsr1" addr add fee1:4::1/64 dev veth1.10 nodad ip netns exec "$nsr1" sysctl net.ipv4.conf.veth1/10.forwarding=1 > /dev/null +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif veth1.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif veth1.10 accept' ip -net "$nsr1" link add name tun0.10 type ipip local 192.168.20.1 remote 192.168.20.2 @@ -649,6 +655,8 @@ ip -net "$nsr1" link set tun0.10 up ip -net "$nsr1" addr add 192.168.200.1/24 dev tun0.10 ip -net "$nsr1" route change default via 192.168.200.2 ip netns exec "$nsr1" sysctl net.ipv4.conf.tun0/10.forwarding=1 > /dev/null +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif tun0.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0.10 accept' ip -net "$nsr1" link add name tun6.10 type ip6tnl local fee1:4::1 remote fee1:4::2 encaplimit none @@ -656,6 +664,8 @@ ip -net "$nsr1" link set tun6.10 up ip -net "$nsr1" addr add fee1:5::1/64 dev tun6.10 nodad ip -6 -net "$nsr1" route delete default ip -6 -net "$nsr1" route add default via fee1:5::2 +ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif tun6.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept' ip -net "$nsr2" link add link veth0 name veth0.10 type vlan id 10 @@ -683,7 +693,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel over vlan"; then fi if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then - echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel over vlan" + check_counters "flow offload for ns1/ns2 IP6IP6 tunnel over vlan" else echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel over vlan" 1>&2 ip netns exec "$nsr1" nft list ruleset @@ -736,6 +746,61 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "on bridge"; then ret=1 fi +if ip -net "$nsr1" link show tun0 > /dev/null 2>&1 && + ip -net "$nsr2" link show tun0 > /dev/null 2>&1; then + ip -net "$nsr1" route change default via 192.168.100.2 + ip -net "$nsr2" route change default via 192.168.100.1 + ip -6 -net "$nsr1" route delete default + ip -6 -net "$nsr1" route add default via fee1:3::2 + ip -6 -net "$nsr2" route delete default + ip -6 -net "$nsr2" route add default via fee1:3::1 + ip -net "$ns2" route add default via 10.0.2.1 + ip -6 -net "$ns2" route add default via dead:2::1 + + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "tun0" tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "tun6" tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "br0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "tun0" accept' + ip netns exec "$nsr1" nft -a insert rule inet filter forward \ + 'meta oif "tun6" accept' + + ip netns exec "$nsr1" nft reset counters table inet filter >/dev/null + + if test_tcp_forwarding "$ns1" "$ns2" 1 4 10.0.2.99 12345; then + check_counters "bridge + IPIP tunnel" + else + echo "FAIL: flow offload for ns1/ns2 with bridge + IPIP tunnel" 1>&2 + ip netns exec "$nsr1" nft list ruleset + ret=1 + fi + + if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then + check_counters "bridge + IP6IP6 tunnel" + else + echo "FAIL: flow offload for ns1/ns2 with bridge + IP6IP6 tunnel" 1>&2 + ip netns exec "$nsr1" nft list ruleset + ret=1 + fi + + ip -net "$nsr1" route change default via 192.168.10.2 + ip -net "$nsr2" route change default via 192.168.10.1 + ip -net "$ns2" route del default via 10.0.2.1 + ip -6 -net "$nsr1" route delete default + ip -6 -net "$nsr1" route add default via fee1:2::2 + ip -6 -net "$nsr2" route delete default + ip -6 -net "$nsr2" route add default via fee1:2::1 + ip -6 -net "$ns2" route del default via dead:2::1 +else + echo "SKIP: bridge + tunnel flowtable regression (tun0 missing)" + [ "$ret" -eq 0 ] && ret=$ksft_skip +fi + # Another test: # Add bridge interface br0 to Router1, with NAT and VLAN. diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config new file mode 100644 index 000000000000..c659749cd086 --- /dev/null +++ b/tools/testing/selftests/net/openvswitch/config @@ -0,0 +1,16 @@ +CONFIG_GENEVE=m +CONFIG_INET_DIAG=y +CONFIG_IPV6=y +CONFIG_NETFILTER=y +CONFIG_NET_IPGRE=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_OVS=y +CONFIG_OPENVSWITCH=m +CONFIG_OPENVSWITCH_GENEVE=m +CONFIG_OPENVSWITCH_GRE=m +CONFIG_OPENVSWITCH_VXLAN=m +CONFIG_PSAMPLE=m +CONFIG_VETH=y +CONFIG_VLAN_8021Q=y +CONFIG_VXLAN=m diff --git a/tools/testing/selftests/net/ovpn/config b/tools/testing/selftests/net/ovpn/config index d6cf033d555e..6b424762e46e 100644 --- a/tools/testing/selftests/net/ovpn/config +++ b/tools/testing/selftests/net/ovpn/config @@ -4,6 +4,7 @@ CONFIG_CRYPTO_CHACHA20POLY1305=y CONFIG_CRYPTO_GCM=y CONFIG_DST_CACHE=y CONFIG_INET=y +CONFIG_IPV6=y CONFIG_NET=y CONFIG_NETFILTER=y CONFIG_NET_UDP_TUNNEL=y @@ -11,3 +12,4 @@ CONFIG_NF_TABLES=m CONFIG_NF_TABLES_INET=y CONFIG_OVPN=m CONFIG_STREAM_PARSER=y +CONFIG_VETH=y diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index d40953375c86..f4effa7580c0 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -1785,7 +1785,7 @@ static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host, const char *service, const char *vpnip) { int ret; - struct addrinfo *result; + struct addrinfo *result = NULL; struct addrinfo hints = { .ai_family = ovpn->sa_family, .ai_socktype = SOCK_DGRAM, @@ -1809,6 +1809,8 @@ static int ovpn_parse_remote(struct ovpn_ctx *ovpn, const char *host, } memcpy(&ovpn->remote, result->ai_addr, result->ai_addrlen); + freeaddrinfo(result); + result = NULL; } if (vpnip) { diff --git a/tools/testing/selftests/net/ovpn/settings b/tools/testing/selftests/net/ovpn/settings new file mode 100644 index 000000000000..ba4d85f74cd6 --- /dev/null +++ b/tools/testing/selftests/net/ovpn/settings @@ -0,0 +1 @@ +timeout=90 diff --git a/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt new file mode 100644 index 000000000000..3fc2de03658a --- /dev/null +++ b/tools/testing/selftests/net/packetdrill/tcp_rfc5961_rst-syn-recv.pkt @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// RFC 9293 Section 3.10.7.4: in SYN-RECEIVED, an exact RST resets +// the connection. A non-exact in-window RST elicits a challenge ACK, +// while an out-of-window RST is silently discarded. + +`./defaults.sh` + +// An exact RST removes the request socket. + 0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0> + +0 > S. 0:0(0) ack 1 <...> + +0 < R 1:1(0) win 1000 + +.1 < . 1:1(0) ack 1 win 1000 + +0 > R 1:1(0) + +0 close(3) = 0 + +// A non-exact in-window RST gets a challenge ACK and the request survives. + +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0> + +0 > S. 0:0(0) ack 1 <...> + +0 < R 2:2(0) win 1000 + +0 > . 1:1(0) ack 1 + +0 < . 1:1(0) ack 1 win 1000 + +0 accept(3, ..., ...) = 4 + +0 close(4) = 0 + +0 close(3) = 0 + +// RST sequence validation precedes ACK validation. Even an RST|ACK +// with an unacceptable ACK value gets a challenge ACK. + +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0> + +0 > S. 0:0(0) ack 1 <...> + +0 < R. 2:2(0) ack 100 win 1000 + +0 > . 1:1(0) ack 1 + +0 < . 1:1(0) ack 1 win 1000 + +0 accept(3, ..., ...) = 4 + +0 close(4) = 0 + +0 close(3) = 0 + +// An out-of-window RST is silent and does not remove the request. + +0 socket(..., SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP) = 3 + +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 + +0 bind(3, ..., ...) = 0 + +0 listen(3, 1) = 0 + +0 < S 0:0(0) win 1000 <mss 1000,sackOK,nop,nop,nop,wscale 0> + +0 > S. 0:0(0) ack 1 <...> + +0 < R 100001:100001(0) win 1000 + +.1 < . 1:1(0) ack 1 win 1000 + +0 accept(3, ..., ...) = 4 + +0 close(4) = 0 + +0 close(3) = 0 diff --git a/tools/testing/selftests/net/tcp_mmap.c b/tools/testing/selftests/net/tcp_mmap.c index 4fcce5150850..2544ae35d07a 100644 --- a/tools/testing/selftests/net/tcp_mmap.c +++ b/tools/testing/selftests/net/tcp_mmap.c @@ -313,6 +313,8 @@ end: tcp_info_get_rcv_mss(fd)); } error: + if (ctx) + EVP_MD_CTX_free(ctx); munmap(buffer, buffer_sz); close(fd); if (zflg) @@ -606,6 +608,8 @@ int main(int argc, char *argv[]) EVP_DigestFinal_ex(ctx, digest, &digest_len); send(fd, digest, (size_t)SHA256_DIGEST_LENGTH, 0); } + if (ctx) + EVP_MD_CTX_free(ctx); close(fd); munmap(buffer, buffer_sz); return 0; diff --git a/tools/testing/selftests/net/tls.c b/tools/testing/selftests/net/tls.c index cbdd3ea28b99..9d3cd4fff062 100644 --- a/tools/testing/selftests/net/tls.c +++ b/tools/testing/selftests/net/tls.c @@ -24,6 +24,7 @@ #include "kselftest_harness.h" #define TLS_PAYLOAD_MAX_LEN 16384 +#define TLS_HDR_LEN 5 #define SOL_TLS 282 static int fips_enabled; @@ -835,6 +836,43 @@ TEST_F(tls, send_and_splice) EXPECT_EQ(memcmp(mem_send, mem_recv, send_len), 0); } +TEST_F(tls, splice_onto_full_record) +{ + char mem_send[4608]; + char mem_recv[4608]; + int frag_len = 100; + int nfrags, i, off; + int p[2]; + + memrnd(mem_send, sizeof(mem_send)); + ASSERT_GE(pipe(p), 0); + + for (nfrags = 16; nfrags <= 44; nfrags++) { + for (i = 0, off = 0; i < nfrags; i++, off += frag_len) { + EXPECT_EQ(write(p[1], mem_send + off, frag_len), frag_len); + EXPECT_EQ(splice(p[0], NULL, self->fd, NULL, frag_len, + SPLICE_F_MORE), frag_len); + } + + EXPECT_EQ(send(self->fd, mem_send + off, 1, MSG_MORE), 1); + off++; + + EXPECT_EQ(write(p[1], mem_send + off, frag_len), frag_len); + EXPECT_EQ(splice(p[0], NULL, self->fd, NULL, frag_len, + SPLICE_F_MORE), frag_len); + off += frag_len; + + EXPECT_EQ(send(self->fd, mem_send + off, 1, 0), 1); + off++; + + EXPECT_EQ(recv(self->cfd, mem_recv, off, MSG_WAITALL), off); + EXPECT_EQ(memcmp(mem_send, mem_recv, off), 0); + } + + close(p[0]); + close(p[1]); +} + TEST_F(tls, splice_to_pipe) { int send_len = TLS_PAYLOAD_MAX_LEN; @@ -1802,6 +1840,63 @@ TEST_F(tls, recv_efault) } #define TLS_RECORD_TYPE_HANDSHAKE 0x16 + +TEST_F(tls_basic, recvmsg_nopad_retry_iov) +{ + char payload[32]; + char first_iov[sizeof(payload)]; + char later_iov[sizeof(payload) * 2]; + char expected_later_iov[sizeof(later_iov)]; + char cbuf[CMSG_SPACE(sizeof(char))]; + struct tls_crypto_info_keys tls13; + struct iovec iov[] = { + { .iov_base = first_iov, .iov_len = sizeof(first_iov) }, + { .iov_base = later_iov, .iov_len = sizeof(later_iov) }, + }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = ARRAY_SIZE(iov), + .msg_control = cbuf, + .msg_controllen = sizeof(cbuf), + }; + int one = 1; + int ret; + int i; + + if (self->notls) + SKIP(return, "no TLS support"); + + tls_crypto_info_init(TLS_1_3_VERSION, TLS_CIPHER_AES_GCM_128, + &tls13, 0); + + ret = setsockopt(self->fd, SOL_TLS, TLS_TX, &tls13, tls13.len); + ASSERT_EQ(ret, 0); + + ret = setsockopt(self->cfd, SOL_TLS, TLS_RX, &tls13, tls13.len); + ASSERT_EQ(ret, 0); + + ret = setsockopt(self->cfd, SOL_TLS, TLS_RX_EXPECT_NO_PAD, + &one, sizeof(one)); + ASSERT_EQ(ret, 0); + + for (i = 0; i < sizeof(payload); i++) + payload[i] = 0x40 + i; + memset(first_iov, 0xa5, sizeof(first_iov)); + memset(later_iov, 0x5a, sizeof(later_iov)); + memset(expected_later_iov, 0x5a, sizeof(expected_later_iov)); + + /* A control record forces optimistic TLS 1.3 RX to retry. */ + ret = tls_send_cmsg(self->fd, TLS_RECORD_TYPE_HANDSHAKE, + payload, sizeof(payload), 0); + ASSERT_EQ(ret, sizeof(payload)); + + ret = recvmsg(self->cfd, &msg, 0); + ASSERT_EQ(ret, sizeof(payload)); + EXPECT_EQ(memcmp(first_iov, payload, sizeof(payload)), 0); + EXPECT_EQ(memcmp(later_iov, expected_later_iov, + sizeof(later_iov)), 0); +} + /* key_update, length 1, update_not_requested */ static const char key_update_msg[] = "\x18\x00\x00\x01\x00"; static void tls_send_keyupdate(struct __test_metadata *_metadata, int fd) @@ -2638,28 +2733,83 @@ TEST_F(tls_err, bad_rec) EXPECT_EQ(errno, EAGAIN); } +/* cfd carries a byte stream, so one recv() can return part of a + * record. Take the fragment length from the record header and wait + * for the remainder. + */ +static void tls_send_bad_auth(struct __test_metadata *_metadata, + int fd, int cfd, int fd2) +{ + char buf[128]; + int len; + + memrnd(buf, sizeof(buf) / 2); + ASSERT_EQ(send(fd, buf, sizeof(buf) / 2, 0), sizeof(buf) / 2); + + ASSERT_EQ(recv(cfd, buf, TLS_HDR_LEN, MSG_WAITALL), TLS_HDR_LEN); + + len = ((unsigned char)buf[3] << 8) | (unsigned char)buf[4]; + ASSERT_GT(len, 0); + ASSERT_LE(len, (int)sizeof(buf) - TLS_HDR_LEN); + + ASSERT_EQ(recv(cfd, buf + TLS_HDR_LEN, len, MSG_WAITALL), len); + + buf[TLS_HDR_LEN + len - 1]++; + + ASSERT_EQ(send(fd2, buf, TLS_HDR_LEN + len, 0), TLS_HDR_LEN + len); +} + TEST_F(tls_err, bad_auth) { char buf[128]; - int n; if (self->notls) SKIP(return, "no TLS support"); - memrnd(buf, sizeof(buf) / 2); - EXPECT_EQ(send(self->fd, buf, sizeof(buf) / 2, 0), sizeof(buf) / 2); - n = recv(self->cfd, buf, sizeof(buf), 0); - EXPECT_GT(n, sizeof(buf) / 2); - - buf[n - 1]++; + tls_send_bad_auth(_metadata, self->fd, self->cfd, self->fd2); - EXPECT_EQ(send(self->fd2, buf, n, 0), n); EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1); EXPECT_EQ(errno, EBADMSG); EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1); EXPECT_EQ(errno, EBADMSG); } +/* A record that did not authenticate breaks the connection for every + * reader, splice included. + * + * The two decrypt paths reach that result differently. A synchronous + * decrypt leaves the record parsed, so the splice re-runs the decrypt + * and fails on the record itself; the ctx->async_wait.err check in + * tls_sw_splice_read() is not what stops it. Only an asynchronous + * decrypt, which needs a TLS 1.2 socket and an AEAD advertising + * CRYPTO_ALG_ASYNC, consumes the record before the failure is + * recorded, leaving that check the sole reason the splice fails. + */ +TEST_F(tls_err, bad_auth_splice) +{ + char buf[128]; + ssize_t ret; + int p[2]; + + if (self->notls) + SKIP(return, "no TLS support"); + + tls_send_bad_auth(_metadata, self->fd, self->cfd, self->fd2); + + EXPECT_EQ(recv(self->cfd2, buf, sizeof(buf), 0), -1); + EXPECT_EQ(errno, EBADMSG); + + ASSERT_GE(pipe(p), 0); + + ret = splice(self->cfd2, NULL, p[1], NULL, sizeof(buf), + SPLICE_F_NONBLOCK); + EXPECT_EQ(ret, -1); + EXPECT_EQ(errno, EBADMSG); + + close(p[0]); + close(p[1]); +} + TEST_F(tls_err, bad_in_large_read) { char txt[3][64]; @@ -2915,7 +3065,6 @@ static size_t parse_tls_records(struct __test_metadata *_metadata, { const __u8 *rec = rx_buf; size_t total_plaintext_rx = 0; - const __u8 rec_header_len = 5; while (rec < rx_buf + rx_len) { __u16 record_payload_len; @@ -2935,7 +3084,7 @@ static size_t parse_tls_records(struct __test_metadata *_metadata, /* Plaintext must not exceed the specified limit */ ASSERT_LE(plaintext_len, max_payload_len); - rec += rec_header_len + record_payload_len; + rec += TLS_HDR_LEN + record_payload_len; } return total_plaintext_rx; diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index cf106a49b55e..abe488bac50b 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -42,19 +42,19 @@ static struct in_addr param_ipaddr4_inner_src = { }; static struct in6_addr param_ipaddr6_outer_dst = { - { { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + { { 0xfd, 0x00, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, }; static struct in6_addr param_ipaddr6_outer_src = { - { { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } }, + { { 0xfd, 0x00, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } }, }; static struct in6_addr param_ipaddr6_inner_dst = { - { { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + { { 0xfd, 0x00, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, }; static struct in6_addr param_ipaddr6_inner_src = { - { { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } }, + { { 0xfd, 0x00, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } }, }; #ifndef BIT diff --git a/tools/testing/selftests/nolibc/Makefile.include b/tools/testing/selftests/nolibc/Makefile.include index c30ca3a9ef14..ea520eac64a6 100644 --- a/tools/testing/selftests/nolibc/Makefile.include +++ b/tools/testing/selftests/nolibc/Makefile.include @@ -5,7 +5,7 @@ _CFLAGS_STACKPROTECTOR ?= $(call try-run, \ echo 'void foo(void) {}' | $(CC) -x c - -o - -S $(CLANG_CROSS_FLAGS) $(__CFLAGS_STACKPROTECTOR) | grep -q __stack_chk_guard, \ $(__CFLAGS_STACKPROTECTOR)) _CFLAGS_SANITIZER ?= $(call cc-option,-fsanitize=undefined -fsanitize-trap=all) -CFLAGS_NOLIBC_TEST ?= -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 \ +CFLAGS_NOLIBC_TEST ?= -Os -fno-ident -fno-asynchronous-unwind-tables -std=c89 -ggdb \ -W -Wall -Wextra -Wundef -Wwrite-strings \ $(call cc-option,-fno-stack-protector) $(call cc-option,-Wmissing-prototypes) \ $(_CFLAGS_STACKPROTECTOR) $(_CFLAGS_SANITIZER) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index 06f881e2e90c..f70c8dfca018 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -112,6 +112,7 @@ EXTRACONFIG_armthumb = -e CONFIG_NAMESPACES EXTRACONFIG_sparc32 = -e CONFIG_TMPFS EXTRACONFIG_m68k = -e CONFIG_BLK_DEV_INITRD EXTRACONFIG_sh4 = -e CONFIG_BLK_DEV_INITRD -e CONFIG_CMDLINE_FROM_BOOTLOADER +EXTRACONFIG_alpha = -e CONFIG_BLK_DEV_INITRD EXTRACONFIG = $(EXTRACONFIG_$(XARCH)) # optional tests to run (default = all) @@ -174,6 +175,7 @@ QEMU_ARGS_m68k = -M virt -append "console=ttyGF0,115200 panic=-1 $(TEST:%= QEMU_ARGS_sh4 = -M r2d -serial file:/dev/stdout -append "console=ttySC1,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_openrisc = -M virt -m 512M -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_parisc32 = -M B160L -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_alpha = -M clipper -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS = -m 1G $(QEMU_ARGS_$(XARCH)) $(QEMU_ARGS_BIOS) $(QEMU_ARGS_EXTRA) # OUTPUT is only set when run from the main makefile, otherwise diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index c1c1ce43a047..ed860b0a15a1 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -756,6 +756,10 @@ int run_startup(int min, int max) /* checking NULL for argv/argv0, environ and _auxv is not enough, let's compare with sbrk(0) or &end */ extern char end; char *brk = sbrk(0) != (void *)-1 ? sbrk(0) : &end; +#if defined(__alpha__) + /* the ordering above does not work on an alpha kernel due to STACK_TOP != TASK_SIZE */ + brk = NULL; +#endif /* differ from nolibc, both glibc and musl have no global _auxv */ const unsigned long *test_auxv = (void *)-1; #ifdef NOLIBC @@ -854,6 +858,58 @@ static int test_dirent(void) return 0; } +int test_getcwd(void) +{ + char cwd_syscall[PATH_MAX]; + char cwd_proc[PATH_MAX]; + ssize_t len; + + /* Read where the link /proc/self/cwd points */ + len = readlink("/proc/self/cwd", cwd_proc, sizeof(cwd_proc) - 1); + if (len <= 0) + return __LINE__; + + /* Terminate the string from readlink() */ + cwd_proc[len] = '\0'; + + /* Get the cwd via syscall */ + if (getcwd(cwd_syscall, sizeof(cwd_syscall)) == NULL) + return __LINE__; + + /* Fail if they aren't the same */ + if (strcmp(cwd_proc, cwd_syscall) != 0) + return __LINE__; + + /* Try getcwd() with NULL for the buffer, + * should return NULL and an error in errno. + * Other libc's allow this by allocating a buffer + * internally. + */ + if (is_nolibc) { + errno = 0; + if (getcwd(NULL, 0) != NULL || !errno) + return __LINE__; + } + + /* Try getcwd() with a buffer but make the size 0, + * should return NULL and an error in errno. + */ + errno = 0; + if (getcwd(cwd_syscall, 0) != NULL || !errno) + return __LINE__; + + /* Try getcwd() with a buffer but make the size 1, + * should return NULL and an error in errno because + * the string written to the buffer is terminated + * so you need at least 2 bytes even for "/". + */ + errno = 0; + if (getcwd(cwd_syscall, 1) != NULL || !errno) + return __LINE__; + + return 0; +} + int test_getrandom(void) { uint64_t rng = 0; @@ -1555,6 +1611,7 @@ int run_syscall(int min, int max) CASE_TEST(clock_getres); EXPECT_SYSZR(1, clock_getres(CLOCK_MONOTONIC, &ts)); break; CASE_TEST(clock_gettime); EXPECT_SYSZR(1, clock_gettime(CLOCK_MONOTONIC, &ts)); break; CASE_TEST(clock_settime); EXPECT_SYSER(1, clock_settime(CLOCK_MONOTONIC, &ts), -1, EINVAL); break; + CASE_TEST(getcwd); EXPECT_SYSZR(proc, test_getcwd()); break; CASE_TEST(getpid); EXPECT_SYSNE(1, getpid(), -1); break; CASE_TEST(getppid); EXPECT_SYSNE(1, getppid(), -1); break; CASE_TEST(gettid); EXPECT_SYSNE(has_gettid, gettid(), -1); break; diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index 6460e25001de..dc0b1649c641 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -30,6 +30,7 @@ all_archs=( m68k sh4 parisc32 + alpha ) archs="${all_archs[@]}" @@ -193,7 +194,7 @@ test_arch() { exit 1 esac printf '%-15s' "$arch:" - if [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" ] && [ "$llvm" = "1" ]; then + if [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" -o "$arch" = "alpha" ] && [ "$llvm" = "1" ]; then echo "Unsupported configuration" return fi diff --git a/tools/testing/selftests/pidfd/pidfd_file_handle_test.c b/tools/testing/selftests/pidfd/pidfd_file_handle_test.c index 68918734dcf3..1e03ae9575fe 100644 --- a/tools/testing/selftests/pidfd/pidfd_file_handle_test.c +++ b/tools/testing/selftests/pidfd/pidfd_file_handle_test.c @@ -373,6 +373,7 @@ TEST_F(file_handle, open_by_handle_at_valid_flags) O_CLOEXEC | O_EXCL); ASSERT_GE(pidfd, 0); + ASSERT_NE(fcntl(pidfd, F_GETFL) & PIDFD_THREAD, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); diff --git a/tools/testing/selftests/proc/proc-pidns.c b/tools/testing/selftests/proc/proc-pidns.c index 25b9a2933c45..6f7c10fe97b3 100644 --- a/tools/testing/selftests/proc/proc-pidns.c +++ b/tools/testing/selftests/proc/proc-pidns.c @@ -6,6 +6,7 @@ #include <assert.h> #include <errno.h> +#include <fcntl.h> #include <sched.h> #include <stdbool.h> #include <stdlib.h> diff --git a/tools/testing/selftests/riscv/vector/validate_v_ptrace.c b/tools/testing/selftests/riscv/vector/validate_v_ptrace.c index 74b6f6bcf067..a388b7963d47 100644 --- a/tools/testing/selftests/riscv/vector/validate_v_ptrace.c +++ b/tools/testing/selftests/riscv/vector/validate_v_ptrace.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only #include <sys/ptrace.h> +#include <sys/syscall.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/uio.h> @@ -25,9 +26,9 @@ TEST(ptrace_v_not_enabled) SKIP(return, "Vector not supported"); chld_lock = 1; - pid = fork(); + pid = (pid_t)syscall(SYS_clone, SIGCHLD, 0, NULL, 0, NULL); ASSERT_LE(0, pid) - TH_LOG("fork: %m"); + TH_LOG("clone: %m"); if (pid == 0) { while (chld_lock == 1) @@ -74,7 +75,7 @@ TEST(ptrace_v_not_enabled) ASSERT_EQ(-1, ret); /* cleanup */ - + free(regset_data); ASSERT_EQ(0, kill(pid, SIGKILL)); } } @@ -206,7 +207,7 @@ TEST(ptrace_v_early_debug) EXPECT_EQ(vl_csr, regset_data->vl); /* cleanup */ - + free(regset_data); ASSERT_EQ(0, kill(pid, SIGKILL)); } } @@ -330,7 +331,7 @@ TEST(ptrace_v_syscall_clobbering) EXPECT_EQ(0UL, regset_data->vl); /* cleanup */ - + free(regset_data); ASSERT_EQ(0, kill(pid, SIGKILL)); } } @@ -648,7 +649,7 @@ TEST_F(v_csr_invalid, ptrace_v_invalid_values) ASSERT_EQ(ret, -1); /* cleanup */ - + free(regset_data); ASSERT_EQ(0, kill(pid, SIGKILL)); } } @@ -910,7 +911,7 @@ TEST_F(v_csr_valid, ptrace_v_valid_values) EXPECT_EQ(regset_data->vlenb, vlenb); /* cleanup */ - + free(regset_data); ASSERT_EQ(0, kill(pid, SIGKILL)); } } diff --git a/tools/testing/selftests/rseq/.gitignore b/tools/testing/selftests/rseq/.gitignore index ec01d164c1f0..9b6eb6c3922f 100644 --- a/tools/testing/selftests/rseq/.gitignore +++ b/tools/testing/selftests/rseq/.gitignore @@ -11,3 +11,5 @@ param_test_mm_cid_benchmark param_test_mm_cid_compare_twice syscall_errors_test slice_test +legacy_check +check_optimized diff --git a/tools/testing/selftests/rseq/Makefile b/tools/testing/selftests/rseq/Makefile index 50d69e22ee7a..aba6317f6cb8 100644 --- a/tools/testing/selftests/rseq/Makefile +++ b/tools/testing/selftests/rseq/Makefile @@ -5,9 +5,13 @@ CLANG_FLAGS += -no-integrated-as endif top_srcdir = ../../../.. +include $(top_srcdir)/scripts/subarch.include +ARCH ?= $(SUBARCH) +LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include CFLAGS += -O2 -Wall -g -I./ $(KHDR_INCLUDES) -L$(OUTPUT) -Wl,-rpath=./ \ - $(CLANG_FLAGS) -I$(top_srcdir)/tools/include + $(CLANG_FLAGS) -I$(top_srcdir)/tools/include \ + -I$(LINUX_TOOL_ARCH_INCLUDE) LDLIBS += -lpthread -ldl # Own dependencies because we only want to build against 1st prerequisite, but diff --git a/tools/testing/selftests/rseq/rseq-x86-thread-pointer.h b/tools/testing/selftests/rseq/rseq-x86-thread-pointer.h index d3133587d996..5a29d6bec51f 100644 --- a/tools/testing/selftests/rseq/rseq-x86-thread-pointer.h +++ b/tools/testing/selftests/rseq/rseq-x86-thread-pointer.h @@ -8,13 +8,11 @@ #ifndef _RSEQ_X86_THREAD_POINTER #define _RSEQ_X86_THREAD_POINTER -#include <features.h> - #ifdef __cplusplus extern "C" { #endif -#if __GNUC_PREREQ (11, 1) +#if __GNUC__ > 11 || (__GNUC__ == 11 && __GNUC_MINOR__ >= 1) static inline void *rseq_thread_pointer(void) { return __builtin_thread_pointer(); diff --git a/tools/testing/selftests/rseq/rseq.c b/tools/testing/selftests/rseq/rseq.c index be0d0a97031e..1cd633923b1d 100644 --- a/tools/testing/selftests/rseq/rseq.c +++ b/tools/testing/selftests/rseq/rseq.c @@ -73,7 +73,7 @@ static int rseq_ownership; #define ORIG_RSEQ_ALLOC_SIZE 32 /* - * Use a union to ensure we allocate a TLS area of 1024 bytes to accomodate an + * Use a union to ensure we allocate a TLS area of 1024 bytes to accommodate an * rseq registration that is larger than the current rseq ABI. */ union rseq_tls { diff --git a/tools/testing/selftests/sched_ext/Makefile b/tools/testing/selftests/sched_ext/Makefile index 5d2dffca0e91..3cfe90e0f34f 100644 --- a/tools/testing/selftests/sched_ext/Makefile +++ b/tools/testing/selftests/sched_ext/Makefile @@ -176,6 +176,7 @@ auto-test-targets := \ maybe_null \ minimal \ non_scx_kfunc_deny \ + nohz_tick \ numa \ allowed_cpus \ peek_dsq \ diff --git a/tools/testing/selftests/sched_ext/nohz_tick.bpf.c b/tools/testing/selftests/sched_ext/nohz_tick.bpf.c new file mode 100644 index 000000000000..6998c5dd6bcb --- /dev/null +++ b/tools/testing/selftests/sched_ext/nohz_tick.bpf.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES + * + * Exercise tick dependency transitions between infinite and finite slices. + */ +#include <scx/common.bpf.h> + +char _license[] SEC("license") = "GPL"; + +const volatile s32 test_cpu; +bool finite_phase; +u64 nr_inf_running; +u64 nr_finite_running; +u64 nr_finite_ticks; + +UEI_DEFINE(uei); + +s32 BPF_STRUCT_OPS(nohz_tick_select_cpu, struct task_struct *p, s32 prev_cpu, + u64 wake_flags) +{ + return prev_cpu; +} + +void BPF_STRUCT_OPS(nohz_tick_enqueue, struct task_struct *p, u64 enq_flags) +{ + u64 slice = finite_phase ? 1000000ULL : SCX_SLICE_INF; + + scx_bpf_dsq_insert(p, SCX_DSQ_GLOBAL, slice, enq_flags); + if (enq_flags & SCX_ENQ_LAST) + scx_bpf_kick_cpu(test_cpu, SCX_KICK_IDLE); +} + +void BPF_STRUCT_OPS(nohz_tick_running, struct task_struct *p) +{ + if (bpf_get_smp_processor_id() != test_cpu) + return; + + if (finite_phase) + __sync_fetch_and_add(&nr_finite_running, 1); + else + __sync_fetch_and_add(&nr_inf_running, 1); +} + +void BPF_STRUCT_OPS(nohz_tick_tick, struct task_struct *p) +{ + if (bpf_get_smp_processor_id() == test_cpu && finite_phase) + __sync_fetch_and_add(&nr_finite_ticks, 1); +} + +void BPF_STRUCT_OPS(nohz_tick_exit, struct scx_exit_info *ei) +{ + UEI_RECORD(uei, ei); +} + +SEC(".struct_ops.link") +struct sched_ext_ops nohz_tick_ops = { + .select_cpu = (void *)nohz_tick_select_cpu, + .enqueue = (void *)nohz_tick_enqueue, + .running = (void *)nohz_tick_running, + .tick = (void *)nohz_tick_tick, + .exit = (void *)nohz_tick_exit, + .name = "nohz_tick", + .timeout_ms = 1000U, +}; diff --git a/tools/testing/selftests/sched_ext/nohz_tick.c b/tools/testing/selftests/sched_ext/nohz_tick.c new file mode 100644 index 000000000000..028f54391c2c --- /dev/null +++ b/tools/testing/selftests/sched_ext/nohz_tick.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES + * + * Validate that a finite-slice EXT task restarts the scheduler tick when it + * follows an infinite-slice EXT task and an idle interval on a NOHZ_FULL CPU. + */ +#define _GNU_SOURCE + +#include <bpf/bpf.h> +#include <errno.h> +#include <sched.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <scx/common.h> + +#include "nohz_tick.bpf.skel.h" +#include "scx_test.h" + +#ifndef SCHED_EXT +#define SCHED_EXT 7 +#endif + +#define MIN_FINITE_TICKS 3 +#define PHASE_TIMEOUT_MS 1000 + +struct nohz_tick_ctx { + struct nohz_tick *skel; + cpu_set_t original_mask; + int test_cpu; +}; + +static int first_allowed_cpu(const cpu_set_t *mask, int first, int last) +{ + int cpu; + + for (cpu = first; cpu <= last && cpu < CPU_SETSIZE; cpu++) + if (CPU_ISSET(cpu, mask)) + return cpu; + + return -1; +} + +static int find_nohz_full_cpu(const cpu_set_t *allowed) +{ + char buf[4096], *cur, *end; + FILE *file; + + file = fopen("/sys/devices/system/cpu/nohz_full", "r"); + if (!file) + return -1; + if (!fgets(buf, sizeof(buf), file)) { + fclose(file); + return -1; + } + fclose(file); + + cur = buf; + while (*cur) { + long first, last; + int cpu; + + while (*cur == ' ' || *cur == '\t' || *cur == ',') + cur++; + if (*cur < '0' || *cur > '9') + break; + + errno = 0; + first = strtol(cur, &end, 10); + if (errno || end == cur || first < 0 || first >= CPU_SETSIZE) + return -1; + cur = end; + last = first; + if (*cur == '-') { + cur++; + errno = 0; + last = strtol(cur, &end, 10); + if (errno || end == cur || last < first) + return -1; + cur = end; + } + + cpu = first_allowed_cpu(allowed, first, last); + if (cpu >= 0) + return cpu; + } + + return -1; +} + +static pid_t start_worker(int cpu) +{ + struct sched_param param = {}; + cpu_set_t mask; + pid_t parent; + pid_t pid; + + parent = getpid(); + pid = fork(); + if (pid != 0) + return pid; + if (prctl(PR_SET_PDEATHSIG, SIGKILL) || getppid() != parent) + _exit(1); + + /* + * Become EXT before touching the target so it stays idle until wakeup. + */ + if (sched_setscheduler(0, SCHED_EXT, ¶m)) + _exit(1); + + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + if (sched_setaffinity(0, sizeof(mask), &mask)) + _exit(1); + + for (;;) + asm volatile("" ::: "memory"); +} + +static void stop_worker(pid_t pid) +{ + if (pid <= 0) + return; + + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); +} + +static int pause_worker(pid_t pid) +{ + int status; + + if (kill(pid, SIGSTOP)) + return -errno; + if (waitpid(pid, &status, WUNTRACED) != pid) + return -errno; + if (!WIFSTOPPED(status)) + return -ECHILD; + + return 0; +} + +static bool wait_for_counter(const u64 *counter, u64 value, int timeout_ms) +{ + int elapsed; + + for (elapsed = 0; elapsed < timeout_ms; elapsed++) { + if (__atomic_load_n(counter, __ATOMIC_RELAXED) >= value) + return true; + usleep(1000); + } + + return false; +} + +static enum scx_test_status setup(void **ctx_ptr) +{ + struct nohz_tick_ctx *ctx; + cpu_set_t controller_mask; + int cpu; + + ctx = calloc(1, sizeof(*ctx)); + SCX_FAIL_IF(!ctx, "Failed to allocate context"); + if (sched_getaffinity(0, sizeof(ctx->original_mask), + &ctx->original_mask)) { + free(ctx); + SCX_FAIL("Failed to get affinity (%d)", errno); + } + + cpu = find_nohz_full_cpu(&ctx->original_mask); + if (cpu < 0) { + fprintf(stderr, "SKIP: no allowed NOHZ_FULL CPU\n"); + free(ctx); + return SCX_TEST_SKIP; + } + + controller_mask = ctx->original_mask; + CPU_CLR(cpu, &controller_mask); + if (CPU_COUNT(&controller_mask) == 0) { + fprintf(stderr, "SKIP: no housekeeping CPU available\n"); + free(ctx); + return SCX_TEST_SKIP; + } + + ctx->test_cpu = cpu; + ctx->skel = nohz_tick__open(); + if (!ctx->skel) { + free(ctx); + SCX_FAIL("Failed to open skeleton"); + } + + SCX_ENUM_INIT(ctx->skel); + ctx->skel->rodata->test_cpu = cpu; + ctx->skel->struct_ops.nohz_tick_ops->flags |= SCX_OPS_SWITCH_PARTIAL | + SCX_OPS_ENQ_LAST; + if (nohz_tick__load(ctx->skel)) { + nohz_tick__destroy(ctx->skel); + free(ctx); + SCX_FAIL("Failed to load skeleton"); + } + + if (sched_setaffinity(0, sizeof(controller_mask), &controller_mask)) { + nohz_tick__destroy(ctx->skel); + free(ctx); + SCX_FAIL("Failed to move controller off CPU %d (%d)", cpu, errno); + } + + *ctx_ptr = ctx; + return SCX_TEST_PASS; +} + +static enum scx_test_status run(void *ctx_ptr) +{ + struct nohz_tick_ctx *ctx = ctx_ptr; + struct nohz_tick *skel = ctx->skel; + struct bpf_link *link = NULL; + enum scx_test_status status = SCX_TEST_FAIL; + pid_t finite_worker = -1; + pid_t inf_worker = -1; + u64 finite_running; + u64 finite_ticks; + int ret; + + link = bpf_map__attach_struct_ops(skel->maps.nohz_tick_ops); + if (!link) { + SCX_ERR("Failed to attach scheduler"); + goto out; + } + + /* + * Establish SCX_RQ_CAN_STOP_TICK with an infinite-slice task. + */ + inf_worker = start_worker(ctx->test_cpu); + if (inf_worker < 0) { + SCX_ERR("Failed to start infinite-slice worker (%d)", errno); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_inf_running, 1, + PHASE_TIMEOUT_MS)) { + SCX_ERR("Infinite-slice worker was not scheduled"); + goto out; + } + + /* Block without exiting so the rq retains the infinite-slice state. */ + ret = pause_worker(inf_worker); + if (ret) { + SCX_ERR("Failed to stop infinite-slice worker (%d)", ret); + goto out; + } + + /* Let the target enter idle with its tick stopped. */ + usleep(100000); + + /* + * The next EXT task receives a finite slice and must restart the tick. + */ + __atomic_store_n(&skel->bss->finite_phase, true, __ATOMIC_RELEASE); + finite_worker = start_worker(ctx->test_cpu); + if (finite_worker < 0) { + SCX_ERR("Failed to start finite-slice worker (%d)", errno); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_finite_running, 1, + PHASE_TIMEOUT_MS)) { + SCX_ERR("Finite-slice worker was not scheduled"); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_finite_ticks, MIN_FINITE_TICKS, + PHASE_TIMEOUT_MS)) { + SCX_ERR("Finite-slice worker received only %llu scheduler ticks", + (unsigned long long)skel->bss->nr_finite_ticks); + goto out; + } + stop_worker(finite_worker); + finite_worker = -1; + + /* + * Leave the CPU idle after a finite-slice task. The next finite-slice + * task must restart the tick even though the slice type is unchanged. + */ + usleep(100000); + finite_running = __atomic_load_n(&skel->bss->nr_finite_running, + __ATOMIC_RELAXED); + finite_ticks = __atomic_load_n(&skel->bss->nr_finite_ticks, + __ATOMIC_RELAXED); + + finite_worker = start_worker(ctx->test_cpu); + if (finite_worker < 0) { + SCX_ERR("Failed to start second finite-slice worker (%d)", errno); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_finite_running, + finite_running + 1, PHASE_TIMEOUT_MS)) { + SCX_ERR("Second finite-slice worker was not scheduled"); + goto out; + } + if (!wait_for_counter(&skel->bss->nr_finite_ticks, + finite_ticks + MIN_FINITE_TICKS, + PHASE_TIMEOUT_MS)) { + SCX_ERR("Second finite-slice worker received only %llu scheduler ticks", + (unsigned long long)(skel->bss->nr_finite_ticks - + finite_ticks)); + goto out; + } + + if (skel->data->uei.kind != EXIT_KIND(SCX_EXIT_NONE)) { + SCX_ERR("Scheduler exited unexpectedly (kind=%llu code=%lld)", + (unsigned long long)skel->data->uei.kind, + (long long)skel->data->uei.exit_code); + goto out; + } + + fprintf(stderr, "CPU %d received %llu finite-slice ticks\n", + ctx->test_cpu, + (unsigned long long)skel->bss->nr_finite_ticks); + status = SCX_TEST_PASS; +out: + stop_worker(finite_worker); + stop_worker(inf_worker); + if (link) + bpf_link__destroy(link); + return status; +} + +static void cleanup(void *ctx_ptr) +{ + struct nohz_tick_ctx *ctx = ctx_ptr; + + sched_setaffinity(0, sizeof(ctx->original_mask), &ctx->original_mask); + nohz_tick__destroy(ctx->skel); + free(ctx); +} + +struct scx_test nohz_tick = { + .name = "nohz_tick", + .description = "Verify finite EXT slices restart the NOHZ_FULL tick", + .setup = setup, + .run = run, + .cleanup = cleanup, +}; +REGISTER_SCX_TEST(&nohz_tick) diff --git a/tools/testing/selftests/sched_ext/numa.bpf.c b/tools/testing/selftests/sched_ext/numa.bpf.c index 78cc49a7f9a6..6b4515c28aa0 100644 --- a/tools/testing/selftests/sched_ext/numa.bpf.c +++ b/tools/testing/selftests/sched_ext/numa.bpf.c @@ -34,7 +34,8 @@ static bool is_cpu_idle(s32 cpu, int node) s32 BPF_STRUCT_OPS(numa_select_cpu, struct task_struct *p, s32 prev_cpu, u64 wake_flags) { - int node = __COMPAT_scx_bpf_cpu_node(scx_bpf_task_cpu(p)); + s32 task_cpu = scx_bpf_task_cpu(p); + int node = __COMPAT_scx_bpf_cpu_node(task_cpu); s32 cpu; /* @@ -48,6 +49,16 @@ s32 BPF_STRUCT_OPS(numa_select_cpu, cpu = __COMPAT_scx_bpf_pick_any_cpu_node(p->cpus_ptr, node, __COMPAT_SCX_PICK_IDLE_IN_NODE); + /* + * @task_cpu may be outside of p->cpus_ptr if @p's affinity + * changed while it was sleeping. This means it's possible for + * p->cpus_ptr to not include any CPUs from @node. + * If we failed to find a cpu in @node, check if @task_cpu + * is outside of p->cpus_ptr and just return @prev_cpu if it is. + */ + if (cpu < 0 && !bpf_cpumask_test_cpu(task_cpu, p->cpus_ptr)) + return prev_cpu; + if (is_cpu_idle(cpu, node)) scx_bpf_error("CPU %d should be marked as busy", cpu); diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c index 358b6c65e120..0622bc2acad4 100644 --- a/tools/testing/selftests/seccomp/seccomp_bpf.c +++ b/tools/testing/selftests/seccomp/seccomp_bpf.c @@ -5178,7 +5178,8 @@ FIXTURE_SETUP(UPROBE) ASSERT_GE(bit, 0); } - offset = get_uprobe_offset(variant->uretprobe ? probed_uretprobe : probed_uprobe); + offset = get_uprobe_offset(variant->uretprobe ? (void *)probed_uretprobe + : (void *)probed_uprobe); ASSERT_GE(offset, 0); if (variant->uretprobe) diff --git a/tools/testing/selftests/syscall_user_dispatch/config b/tools/testing/selftests/syscall_user_dispatch/config index 039e303e59d7..22c4dfe167ca 100644 --- a/tools/testing/selftests/syscall_user_dispatch/config +++ b/tools/testing/selftests/syscall_user_dispatch/config @@ -1 +1 @@ -CONFIG_GENERIC_ENTRY=y +CONFIG_SYSCALL_USER_DISPATCH=y diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/ct.json b/tools/testing/selftests/tc-testing/tc-tests/actions/ct.json index da65f838bd52..8ab48def89b6 100644 --- a/tools/testing/selftests/tc-testing/tc-tests/actions/ct.json +++ b/tools/testing/selftests/tc-testing/tc-tests/actions/ct.json @@ -702,5 +702,45 @@ "$TC qdisc del dev $DUMMY clsact", "$TC qdisc del dev $DUMMY root handle 1:" ] + }, + { + "id": "c7a3", + "name": "Verify act_ct drops a packet whose header checks fail", + "category": [ + "actions", + "ct", + "scapy" + ], + "plugins": { + "requires": [ + "nsPlugin", + "scapyPlugin" + ] + }, + "setup": [ + [ + "$TC qdisc del dev $DEV1 clsact", + 0, + 1, + 2, + 255 + ], + "$TC qdisc add dev $DEV1 clsact" + ], + "cmdUnderTest": "$TC filter add dev $DEV1 ingress protocol all prio 1 matchall action ct", + "scapy": [ + { + "iface": "$DEV0", + "count": 10, + "packet": "Ether(type=0x86dd)/IPv6(nh=0, plen=0, src='::1', dst='::2')" + } + ], + "expExitCode": "0", + "verifyCmd": "$TC -s qdisc show dev $DEV1 clsact", + "matchPattern": "dropped 10", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DEV1 clsact" + ] } ] diff --git a/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json new file mode 100644 index 000000000000..48f8683f0d1a --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/qdiscs/depth.json @@ -0,0 +1,74 @@ +[ + { + "id": "ad43", + "name": "Create qdisc hierarchy at maximum depth", + "category": [ + "qdisc", + "depth" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY root handle 1: htb default 1", + "$TC class add dev $DUMMY parent 1: classid 1:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 1:1 handle 2: htb default 1", + "$TC class add dev $DUMMY parent 2: classid 2:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 2:1 handle 3: htb default 1", + "$TC class add dev $DUMMY parent 3: classid 3:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 3:1 handle 4: htb default 1", + "$TC class add dev $DUMMY parent 4: classid 4:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 4:1 handle 5: htb default 1", + "$TC class add dev $DUMMY parent 5: classid 5:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 5:1 handle 6: htb default 1", + "$TC class add dev $DUMMY parent 6: classid 6:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 6:1 handle 7: htb default 1", + "$TC class add dev $DUMMY parent 7: classid 7:1 htb rate 10mbit" + ], + "cmdUnderTest": "$TC qdisc add dev $DUMMY parent 7:1 handle 8: htb default 1", + "expExitCode": "0", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc htb 8: parent 7:1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev $DUMMY root" + ] + }, + { + "id": "c519", + "name": "Reject qdisc hierarchy above maximum depth", + "category": [ + "qdisc", + "depth" + ], + "plugins": { + "requires": "nsPlugin" + }, + "setup": [ + "$TC qdisc add dev $DUMMY root handle 1: htb default 1", + "$TC class add dev $DUMMY parent 1: classid 1:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 1:1 handle 2: htb default 1", + "$TC class add dev $DUMMY parent 2: classid 2:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 2:1 handle 3: htb default 1", + "$TC class add dev $DUMMY parent 3: classid 3:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 3:1 handle 4: htb default 1", + "$TC class add dev $DUMMY parent 4: classid 4:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 4:1 handle 5: htb default 1", + "$TC class add dev $DUMMY parent 5: classid 5:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 5:1 handle 6: htb default 1", + "$TC class add dev $DUMMY parent 6: classid 6:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 6:1 handle 7: htb default 1", + "$TC class add dev $DUMMY parent 7: classid 7:1 htb rate 10mbit", + "$TC qdisc add dev $DUMMY parent 7:1 handle 8: htb default 1", + "$TC class add dev $DUMMY parent 8: classid 8:1 htb rate 10mbit" + ], + "cmdUnderTest": "$TC qdisc add dev $DUMMY parent 8:1 handle 9: htb default 1", + "expExitCode": "2", + "verifyCmd": "$TC qdisc show dev $DUMMY", + "matchPattern": "qdisc htb 9:", + "matchCount": "0", + "teardown": [ + "$TC qdisc del dev $DUMMY root" + ] + } +] diff --git a/tools/testing/selftests/user_events/abi_test.c b/tools/testing/selftests/user_events/abi_test.c index 85892b3b719c..b71813eaf5c0 100644 --- a/tools/testing/selftests/user_events/abi_test.c +++ b/tools/testing/selftests/user_events/abi_test.c @@ -132,6 +132,33 @@ static int event_delete(void) return ret; } +/* + * Deleting an event drops its last reference, but an unregister may defer + * that put (and the freeing of the associated enabler) past an RCU grace + * period. The delete can therefore transiently fail with -EBUSY while the + * previous reference is still being dropped. Retry only on that transient + * failure; treat an already-deleted event (-ENOENT) as success and return + * any other error immediately rather than spinning for the full timeout. + */ +static int wait_for_event_delete(void) +{ + int i, ret; + + for (i = 0; i < 10000; ++i) { + ret = event_delete(); + + if (ret == 0 || errno == ENOENT) + return 0; + + if (errno != EBUSY) + return ret; + + usleep(1000); + } + + return ret; +} + static int reg_enable_multi(void *enable, int size, int bit, int flags, char *args) { @@ -262,7 +289,7 @@ TEST_F(user, flags) { ASSERT_TRUE(event_exists()); /* Ensure we can delete it */ - ASSERT_EQ(0, event_delete()); + ASSERT_EQ(0, wait_for_event_delete()); /* USER_EVENT_REG_MAX or above is not allowed */ ASSERT_EQ(-1, reg_enable_flags(&self->check, sizeof(int), 0, diff --git a/tools/testing/selftests/user_events/perf_test.c b/tools/testing/selftests/user_events/perf_test.c index cafec0e52eb3..5727cb5b914c 100644 --- a/tools/testing/selftests/user_events/perf_test.c +++ b/tools/testing/selftests/user_events/perf_test.c @@ -85,6 +85,7 @@ static int get_offset(void) static int clear(int *check) { struct user_unreg unreg = {0}; + int i, ret; unreg.size = sizeof(unreg); unreg.disable_bit = 31; @@ -99,13 +100,32 @@ static int clear(int *check) if (errno != ENOENT) return -1; - if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1) - if (errno != ENOENT) + /* + * Deleting the event drops its last reference, but the unregister + * above defers that put (and the freeing of the enabler) past an RCU + * grace period. The delete can therefore transiently fail with -EBUSY + * until that reference is dropped. Retry for up to ~10 seconds so the + * event is actually gone before the next test registers the same name. + */ + for (i = 0; i < 10000; ++i) { + ret = ioctl(fd, DIAG_IOCSDEL, "__test_event"); + + if (ret == 0 || errno == ENOENT) { + ret = 0; + break; + } + + if (errno != EBUSY) { + close(fd); return -1; + } + + usleep(1000); + } close(fd); - return 0; + return ret; } FIXTURE(user) { diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile index e6e8cb52ab03..2c32c48db509 100644 --- a/tools/testing/selftests/vfio/Makefile +++ b/tools/testing/selftests/vfio/Makefile @@ -29,8 +29,6 @@ CFLAGS += $(EXTRA_CFLAGS) LDFLAGS += -pthread -LDLIBS += -luuid - $(TEST_GEN_PROGS): $(OUTPUT)/%: $(OUTPUT)/%.o $(LIBVFIO_O) $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< $(LIBVFIO_O) $(LDLIBS) -o $@ diff --git a/tools/testing/selftests/vfio/lib/libvfio.mk b/tools/testing/selftests/vfio/lib/libvfio.mk index 2b8d73b7d329..67942b085068 100644 --- a/tools/testing/selftests/vfio/lib/libvfio.mk +++ b/tools/testing/selftests/vfio/lib/libvfio.mk @@ -26,6 +26,8 @@ $(LIBVFIO_O_DIRS): CFLAGS += -I$(LIBVFIO_SRCDIR)/include +LDLIBS += -luuid + $(LIBVFIO_O): $(LIBVFIO_OUTPUT)/%.o : $(LIBVFIO_SRCDIR)/%.c | $(LIBVFIO_O_DIRS) $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ diff --git a/tools/testing/selftests/x86/test_shadow_stack.c b/tools/testing/selftests/x86/test_shadow_stack.c index 21af54d5f4ea..3d6ca33edba4 100644 --- a/tools/testing/selftests/x86/test_shadow_stack.c +++ b/tools/testing/selftests/x86/test_shadow_stack.c @@ -873,6 +873,86 @@ out: return err; } +/* Keep the CALL first so the function address is exactly the probed CALL. */ +extern void uprobe_call_trigger(void); +asm (".pushsection .text\n" + ".global uprobe_call_target\n" + ".type uprobe_call_target, @function\n" + "uprobe_call_target:\n" + " ret\n" + ".size uprobe_call_target, .-uprobe_call_target\n" + + ".global uprobe_call_trigger\n" + ".type uprobe_call_trigger, @function\n" + "uprobe_call_trigger:\n" + " call uprobe_call_target\n" + " ret\n" + ".size uprobe_call_trigger, .-uprobe_call_trigger\n" + ".popsection\n" +); + +/* If CALL emulation misses the shadow stack update, this exits via SIGSEGV. */ +static int test_uprobe_call(void) +{ + const size_t attr_sz = sizeof(struct perf_event_attr); + const char *file = "/proc/self/exe"; + int fd = -1, type, err = 1; + struct perf_event_attr attr; + struct sigaction sa = {}; + ssize_t offset; + + type = determine_uprobe_perf_type(); + if (type < 0) { + if (type == -ENOENT) + printf("[SKIP]\tUprobe on CALL test, uprobes are not available\n"); + return 0; + } + + offset = get_uprobe_offset(uprobe_call_trigger); + if (offset < 0) + return 1; + + sa.sa_sigaction = segv_gp_handler; + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGSEGV, &sa, NULL)) + return 1; + + /* Setup entry uprobe through perf event interface. */ + memset(&attr, 0, attr_sz); + attr.size = attr_sz; + attr.type = type; + attr.config = 0; + attr.config1 = (__u64)(unsigned long)file; + attr.config2 = offset; + + fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */, + -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC); + if (fd < 0) + goto out; + + if (sigsetjmp(jmp_buffer, 1)) + goto out; + + if (ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK)) + goto out; + + /* + * This either segfaults and goes through sigsetjmp above + * or succeeds and we're good. + */ + uprobe_call_trigger(); + + printf("[OK]\tUprobe on CALL test\n"); + err = 0; + +out: + ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); + signal(SIGSEGV, SIG_DFL); + if (fd >= 0) + close(fd); + return err; +} + void segv_handler_ptrace(int signum, siginfo_t *si, void *uc) { /* The SSP adjustment caused a segfault. */ @@ -1071,6 +1151,12 @@ int main(int argc, char *argv[]) goto out; } + if (test_uprobe_call()) { + ret = 1; + printf("[FAIL]\tuprobe on CALL test\n"); + goto out; + } + return ret; out: diff --git a/tools/testing/selftests/zram/zram_lib.sh b/tools/testing/selftests/zram/zram_lib.sh index 21ec1966de76..0d44d83888f9 100755 --- a/tools/testing/selftests/zram/zram_lib.sh +++ b/tools/testing/selftests/zram/zram_lib.sh @@ -37,7 +37,7 @@ kernel_gte() if [ $kernel_major -gt $major ]; then return 0 - elif [[ $kernel_major -eq $major && $kernel_minor -ge $minor ]]; then + elif [ $kernel_major -eq $major ] && [ $kernel_minor -ge $minor ]; then return 0 fi diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c index 76be0e4a7f0e..b4ff9f946565 100644 --- a/tools/testing/vsock/vsock_test.c +++ b/tools/testing/vsock/vsock_test.c @@ -2347,6 +2347,88 @@ static void test_stream_tx_credit_bounds_server(const struct test_opts *opts) close(fd); } +/* Test that many small packets don't cause a connection reset under pressure + * and that data integrity is preserved. Packet sizes vary randomly between + * 129 and 512 bytes, above GOOD_COPY_LEN (128) to bypass in-place coalescing + * in recv_enqueue, forcing each one into its own skb. Without receive queue + * collapsing, the per-skb overhead eventually exceeds buf_alloc and the + * connection is reset. + */ +#define COLLAPSE_PKT_MIN 129 +#define COLLAPSE_PKT_MAX 512 +#define COLLAPSE_TOTAL (2 * 1024 * 1024) + +static void test_stream_collapse_client(const struct test_opts *opts) +{ + unsigned char *data; + unsigned long hash; + size_t offset = 0; + int i, fd; + + data = malloc(COLLAPSE_TOTAL); + if (!data) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < COLLAPSE_TOTAL; i++) + data[i] = rand() & 0xff; + + fd = vsock_stream_connect(opts->peer_cid, opts->peer_port); + if (fd < 0) { + perror("connect"); + exit(EXIT_FAILURE); + } + + while (offset < COLLAPSE_TOTAL) { + size_t pkt_size = COLLAPSE_PKT_MIN + + rand() % (COLLAPSE_PKT_MAX - COLLAPSE_PKT_MIN + 1); + + pkt_size = min(pkt_size, COLLAPSE_TOTAL - offset); + + send_buf(fd, data + offset, pkt_size, 0, pkt_size); + offset += pkt_size; + } + + hash = hash_djb2(data, COLLAPSE_TOTAL); + control_writeulong(hash); + + free(data); + close(fd); +} + +static void test_stream_collapse_server(const struct test_opts *opts) +{ + unsigned long hash, remote_hash; + unsigned char *data; + int fd; + + data = malloc(COLLAPSE_TOTAL); + if (!data) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL); + if (fd < 0) { + perror("accept"); + exit(EXIT_FAILURE); + } + + recv_buf(fd, data, COLLAPSE_TOTAL, 0, COLLAPSE_TOTAL); + + hash = hash_djb2(data, COLLAPSE_TOTAL); + remote_hash = control_readulong(); + if (hash != remote_hash) { + fprintf(stderr, "hash mismatch: local %lu remote %lu\n", + hash, remote_hash); + exit(EXIT_FAILURE); + } + + free(data); + close(fd); +} + static struct test_case test_cases[] = { { .name = "SOCK_STREAM connection reset", @@ -2546,6 +2628,11 @@ static struct test_case test_cases[] = { .run_client = test_stream_msg_peek_client, .run_server = test_stream_peek_after_recv_server, }, + { + .name = "SOCK_STREAM small packets backpressure", + .run_client = test_stream_collapse_client, + .run_server = test_stream_collapse_server, + }, {}, }; diff --git a/tools/thermal/thermometer/thermometer.c b/tools/thermal/thermometer/thermometer.c index 022865da8e3c..1ae6c3ecedb4 100644 --- a/tools/thermal/thermometer/thermometer.c +++ b/tools/thermal/thermometer/thermometer.c @@ -272,6 +272,7 @@ static int thermometer_add_tz(const char *path, const char *name, int polling, tz = realloc(thermometer->tz, sizeof(*thermometer->tz) * (thermometer->nr_tz + 1)); if (!tz) { ERROR("Failed to allocate thermometer->tz\n"); + close(fd); return -1; } diff --git a/tools/tracing/rtla/Makefile b/tools/tracing/rtla/Makefile index 60a102538988..387bc6cc18f0 100644 --- a/tools/tracing/rtla/Makefile +++ b/tools/tracing/rtla/Makefile @@ -45,6 +45,9 @@ else LIB_OUTPUT = $(CURDIR)/lib endif +LIB_CTYPE = $(LIB_OUTPUT)/ctype.o +LIB_CTYPE_SRC = $(srctree)/tools/lib/ctype.c + LIB_STRING = $(LIB_OUTPUT)/string.o LIB_STRING_SRC = $(srctree)/tools/lib/string.c @@ -117,12 +120,12 @@ tests/bpf/bpf_action_map.o: tests/bpf/bpf_action_map.c $(Q)echo "BPF skeleton support is disabled, skipping tests/bpf/bpf_action_map.o" endif -$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) - $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS) +$(RTLA): $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) + $(QUIET_LINK)$(CC) $(LDFLAGS) -o $(RTLA) $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS) -static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) +static: $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(eval LDFLAGS += -static) - $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS) + $(QUIET_LINK)$(CC) -static $(LDFLAGS) -o $(RTLA)-static $(RTLA_IN) $(LIBSUBCMD) $(LIB_CTYPE) $(LIB_STRING) $(LIB_STR_ERROR_R) $(EXTLIBS) rtla.%: fixdep FORCE make -f $(srctree)/tools/build/Makefile.build dir=. $@ @@ -150,6 +153,9 @@ $(LIB_STR_ERROR_R): $(LIB_STR_ERROR_R_SRC) | $(LIB_OUTPUT) $(LIB_STRING): $(LIB_STRING_SRC) | $(LIB_OUTPUT) $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $< +$(LIB_CTYPE): $(LIB_CTYPE_SRC) | $(LIB_OUTPUT) + $(QUIET_CC)$(CC) $(CFLAGS) -c -o $@ $< + libsubcmd-clean: $(call QUIET_CLEAN, libsubcmd) $(Q)$(RM) -r -- $(LIBSUBCMD_OUTPUT) diff --git a/tools/tracing/rtla/src/common.c b/tools/tracing/rtla/src/common.c index d0a8a6edbf0c..8c7f5e75b2ec 100644 --- a/tools/tracing/rtla/src/common.c +++ b/tools/tracing/rtla/src/common.c @@ -5,6 +5,7 @@ #include <signal.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> #include <sys/sysinfo.h> #include "common.h" diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index 18e1071a2e24..6206a0a565ad 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -536,7 +536,7 @@ timerlat_top_bpf_main_loop(struct osnoise_tool *tool) if (!params->quiet) timerlat_print_stats(tool); - if (wait_retval != 0) { + if (wait_retval > 0) { /* Stopping requested by tracer */ retval = common_threshold_handler(tool); if (retval) diff --git a/tools/usb/usbip/libsrc/usbip_common.c b/tools/usb/usbip/libsrc/usbip_common.c index b8d7d480595a..f4734f552d31 100644 --- a/tools/usb/usbip/libsrc/usbip_common.c +++ b/tools/usb/usbip/libsrc/usbip_common.c @@ -29,6 +29,8 @@ static const struct speed_string speed_strings[] = { { USB_SPEED_HIGH, "480", "High Speed(480Mbps)" }, { USB_SPEED_WIRELESS, "53.3-480", "Wireless"}, { USB_SPEED_SUPER, "5000", "Super Speed(5000Mbps)" }, + { USB_SPEED_SUPER_PLUS, "10000", "Super Speed Plus(10000Mbps)" }, + { USB_SPEED_SUPER_PLUS, "20000", "Super Speed Plus(20000Mbps)" }, { 0, NULL, NULL } }; diff --git a/tools/usb/usbip/libsrc/usbip_device_driver.c b/tools/usb/usbip/libsrc/usbip_device_driver.c index 1dfbb76ab26c..c9b3619d86f3 100644 --- a/tools/usb/usbip/libsrc/usbip_device_driver.c +++ b/tools/usb/usbip/libsrc/usbip_device_driver.c @@ -57,6 +57,10 @@ static struct { .speed = USB_SPEED_SUPER, .name = "super-speed", }, + { + .speed = USB_SPEED_SUPER_PLUS, + .name = "super-speed-plus", + }, }; static diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c index 8159fd98680b..4ca3783ee5b7 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.c +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -338,6 +338,7 @@ int usbip_vhci_get_free_port(uint32_t speed) switch (speed) { case USB_SPEED_SUPER: + case USB_SPEED_SUPER_PLUS: if (vhci_driver->idev[i].hub != HUB_SPEED_SUPER) continue; break; diff --git a/tools/virtio/asm/percpu_types.h b/tools/virtio/asm/percpu_types.h new file mode 100644 index 000000000000..4eb53d93c099 --- /dev/null +++ b/tools/virtio/asm/percpu_types.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_PERCPU_TYPES_H +#define _ASM_PERCPU_TYPES_H + +#define __percpu_qual + +#endif /* _ASM_PERCPU_TYPES_H */ diff --git a/tools/virtio/linux/completion.h b/tools/virtio/linux/completion.h new file mode 100644 index 000000000000..5e54b679721b --- /dev/null +++ b/tools/virtio/linux/completion.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_COMPLETION_H +#define _LINUX_COMPLETION_H + +struct completion { + unsigned int done; +}; + +#endif /* _LINUX_COMPLETION_H */ diff --git a/tools/virtio/linux/device.h b/tools/virtio/linux/device.h index 075c2140d975..abf100cb0023 100644 --- a/tools/virtio/linux/device.h +++ b/tools/virtio/linux/device.h @@ -1,4 +1,5 @@ #ifndef LINUX_DEVICE_H +#define LINUX_DEVICE_H struct device { void *parent; diff --git a/tools/virtio/linux/dma-mapping.h b/tools/virtio/linux/dma-mapping.h index 8d1a16cb20db..b9fc5e8338e3 100644 --- a/tools/virtio/linux/dma-mapping.h +++ b/tools/virtio/linux/dma-mapping.h @@ -61,5 +61,6 @@ enum dma_data_direction { #define DMA_MAPPING_ERROR (~(dma_addr_t)0) #define DMA_ATTR_CPU_CACHE_CLEAN (1UL << 11) +#define DMA_ATTR_DEBUGGING_IGNORE_CACHELINES 0 #endif diff --git a/tools/virtio/linux/mod_devicetable.h b/tools/virtio/linux/mod_devicetable.h new file mode 100644 index 000000000000..3ba594b8229d --- /dev/null +++ b/tools/virtio/linux/mod_devicetable.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_MOD_DEVICETABLE_H +#define _LINUX_MOD_DEVICETABLE_H + +#include <linux/types.h> + +struct virtio_device_id { + __u32 device; + __u32 vendor; +}; + +#define VIRTIO_DEV_ANY_ID 0xffffffff + +#endif /* _LINUX_MOD_DEVICETABLE_H */ diff --git a/tools/virtio/linux/virtio_features.h b/tools/virtio/linux/virtio_features.h new file mode 100644 index 000000000000..04cbb9622ec7 --- /dev/null +++ b/tools/virtio/linux/virtio_features.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_VIRTIO_FEATURES_H +#define _LINUX_VIRTIO_FEATURES_H + +#include <linux/bug.h> +#include <linux/string.h> +#include <linux/types.h> + +#define VIRTIO_FEATURES_U64S 2 +#define VIRTIO_FEATURES_BITS (VIRTIO_FEATURES_U64S * 64) + +#define VIRTIO_BIT(b) (1ULL << ((b) & 0x3f)) +#define VIRTIO_U64(b) ((b) >> 6) + +#define VIRTIO_DECLARE_FEATURES(name) \ + union { \ + u64 name; \ + u64 name##_array[VIRTIO_FEATURES_U64S];\ + } + +static inline bool virtio_features_chk_bit(unsigned int bit) +{ + return bit < VIRTIO_FEATURES_BITS; +} + +static inline bool virtio_features_test_bit(const u64 *features, + unsigned int bit) +{ + return virtio_features_chk_bit(bit) && + !!(features[VIRTIO_U64(bit)] & VIRTIO_BIT(bit)); +} + +static inline void virtio_features_set_bit(u64 *features, unsigned int bit) +{ + if (virtio_features_chk_bit(bit)) + features[VIRTIO_U64(bit)] |= VIRTIO_BIT(bit); +} + +static inline void virtio_features_clear_bit(u64 *features, unsigned int bit) +{ + if (virtio_features_chk_bit(bit)) + features[VIRTIO_U64(bit)] &= ~VIRTIO_BIT(bit); +} + +static inline void virtio_features_zero(u64 *features) +{ + memset(features, 0, sizeof(features[0]) * VIRTIO_FEATURES_U64S); +} + +static inline void virtio_features_from_u64(u64 *features, u64 from) +{ + virtio_features_zero(features); + features[0] = from; +} + +static inline bool virtio_features_equal(const u64 *f1, const u64 *f2) +{ + int i; + + for (i = 0; i < VIRTIO_FEATURES_U64S; ++i) + if (f1[i] != f2[i]) + return false; + return true; +} + +static inline void virtio_features_copy(u64 *to, const u64 *from) +{ + memcpy(to, from, sizeof(to[0]) * VIRTIO_FEATURES_U64S); +} + +static inline void virtio_features_andnot(u64 *to, const u64 *f1, const u64 *f2) +{ + int i; + + for (i = 0; i < VIRTIO_FEATURES_U64S; i++) + to[i] = f1[i] & ~f2[i]; +} + +#endif /* _LINUX_VIRTIO_FEATURES_H */ |
