| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/21 13:49 | flow | patch-triage |
3mArgs:null Results: {
"EnableConfigs": [
"COREDUMP",
"UNIX",
"BINFMT_ELF",
"ELF_CORE",
"PROC_FS"
],
"FocusSymbols": [
"vfs_coredump"
],
"KMSANReasoning": "The patch modifies the coredump socket protocol to allow dynamic selection of memory types by introducing new fields to `struct coredump_req` and `struct coredump_ack`. \n\nThe `struct coredump_req` is sent to user space, but it is fully initialized on the stack using a designated initializer (`= { ... }`), which ensures all fields are properly initialized. Furthermore, the structure is perfectly aligned and contains no padding bytes (4+4+8+8+8 = 32 bytes), eliminating any risk of padding info-leaks.\n\nThe `struct coredump_ack` is received from user space, and it is explicitly zero-initialized (`= {}`) before the `recv` call, ensuring that any unreceived fields (e.g., when a shorter struct is sent by an older userspace) remain zeroed and do not contain uninitialized stack memory.\n\nThere are no new buffer allocations, no complex data structures with uninitialized fields, and no conditional logic depending on uninitialized variables. Any potential memory safety issues (like out-of-bounds accesses) would be caught by standard KASAN. Therefore, a dedicated KMSAN fuzzing session is not necessary.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the coredump socket protocol to allow the coredump server to dynamically select memory types to include in the core dump. This is a functional change in the core kernel's coredump logic, which is reachable when a process crashes and the core_pattern is set to use a socket. The changes are definitely worth fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/21 13:49 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 324a9577c7e70c4a55951ad6619bab74d40f523a\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Aug 21 13:49:49 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst\nindex 2ccd5b2dfdd33..3a309e8ee96bd 100644\n--- a/Documentation/filesystems/proc.rst\n+++ b/Documentation/filesystems/proc.rst\n@@ -1962,6 +1962,10 @@ For example::\n $ echo 0x7 \u003e /proc/self/coredump_filter\n $ ./some_program\n \n+If the coredump socket protocol is used a coredump server can select memory\n+types to include dynamically. See COREDUMP_MEMORY_TYPES in\n+include/uapi/linux/coredump.h.\n+\n 3.5\t/proc/\u003cpid\u003e/mountinfo - Information about mounts\n --------------------------------------------------------\n \ndiff --git a/fs/coredump.c b/fs/coredump.c\nindex 7b568d25887ce..ec9359e382ea5 100644\n--- a/fs/coredump.c\n+++ b/fs/coredump.c\n@@ -759,10 +759,39 @@ static inline bool coredump_sock_send(struct file *file, struct coredump_req *re\n \treturn ret == sizeof(*req);\n }\n \n-static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER0);\n-static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER0);\n+static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER1);\n+static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER1);\n static_assert(sizeof(enum coredump_mark) == sizeof(__u32));\n \n+/* Every memory type this kernel knows. */\n+#define COREDUMP_MEMORY_ALL\t\t\t\t\t\t\\\n+\t(COREDUMP_MEMORY_ANON_PRIVATE | COREDUMP_MEMORY_ANON_SHARED |\t\\\n+\t COREDUMP_MEMORY_FILE_PRIVATE | COREDUMP_MEMORY_FILE_SHARED |\t\\\n+\t COREDUMP_MEMORY_ELF_HEADERS |\t\t\t\t\t\\\n+\t COREDUMP_MEMORY_HUGETLB_PRIVATE | COREDUMP_MEMORY_HUGETLB_SHARED | \\\n+\t COREDUMP_MEMORY_DAX_PRIVATE | COREDUMP_MEMORY_DAX_SHARED)\n+\n+#define COREDUMP_MEMORY_TYPE_BIT(mmf) BIT((mmf) - MMF_DUMP_FILTER_SHIFT)\n+static_assert(COREDUMP_MEMORY_ALL == (MMF_DUMP_FILTER_MASK \u003e\u003e MMF_DUMP_FILTER_SHIFT));\n+static_assert(COREDUMP_MEMORY_ANON_PRIVATE ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ANON_PRIVATE));\n+static_assert(COREDUMP_MEMORY_ANON_SHARED ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ANON_SHARED));\n+static_assert(COREDUMP_MEMORY_FILE_PRIVATE ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_MAPPED_PRIVATE));\n+static_assert(COREDUMP_MEMORY_FILE_SHARED ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_MAPPED_SHARED));\n+static_assert(COREDUMP_MEMORY_ELF_HEADERS ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ELF_HEADERS));\n+static_assert(COREDUMP_MEMORY_HUGETLB_PRIVATE ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_HUGETLB_PRIVATE));\n+static_assert(COREDUMP_MEMORY_HUGETLB_SHARED ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_HUGETLB_SHARED));\n+static_assert(COREDUMP_MEMORY_DAX_PRIVATE ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_DAX_PRIVATE));\n+static_assert(COREDUMP_MEMORY_DAX_SHARED ==\n+\t COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_DAX_SHARED));\n+\n static inline bool coredump_sock_mark(struct file *file, enum coredump_mark mark)\n {\n \tstruct msghdr msg = { .msg_flags = MSG_NOSIGNAL };\n@@ -804,11 +833,14 @@ static inline void coredump_sock_shutdown(struct file *file)\n static bool coredump_sock_request(struct core_name *cn, struct coredump_params *cprm)\n {\n \tstruct coredump_req req = {\n-\t\t.size\t\t= sizeof(struct coredump_req),\n-\t\t.mask\t\t= COREDUMP_KERNEL | COREDUMP_USERSPACE |\n-\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT |\n-\t\t\t\t COREDUMP_RECORDS | COREDUMP_SPARSE,\n-\t\t.size_ack\t= sizeof(struct coredump_ack),\n+\t\t.size\t\t\t= sizeof(struct coredump_req),\n+\t\t.mask\t\t\t= COREDUMP_KERNEL | COREDUMP_USERSPACE |\n+\t\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT |\n+\t\t\t\t\t COREDUMP_RECORDS | COREDUMP_SPARSE |\n+\t\t\t\t\t COREDUMP_MEMORY_TYPES,\n+\t\t.size_ack\t\t= sizeof(struct coredump_ack),\n+\t\t.memory_types\t\t= cprm-\u003ememory_types,\n+\t\t.memory_types_mask\t= COREDUMP_MEMORY_ALL,\n \t};\n \tstruct coredump_ack ack = {};\n \tssize_t usize;\n@@ -873,6 +905,30 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *\n \t\treturn false;\n \t}\n \n+\tif (ack.mask \u0026 COREDUMP_MEMORY_TYPES) {\n+\t\t/* The memory types need the whole field. */\n+\t\tif (usize \u003c COREDUMP_ACK_SIZE_VER1) {\n+\t\t\tcoredump_sock_mark(cprm-\u003efile, COREDUMP_MARK_MINSIZE);\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\t/* The memory types only select what the kernel writes. */\n+\t\tif (!(ack.mask \u0026 COREDUMP_KERNEL)) {\n+\t\t\tcoredump_sock_mark(cprm-\u003efile, COREDUMP_MARK_CONFLICTING);\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\t/* Refuse unknown memory types. */\n+\t\tif (ack.memory_types \u0026 ~req.memory_types_mask) {\n+\t\t\tcoredump_sock_mark(cprm-\u003efile, COREDUMP_MARK_UNSUPPORTED);\n+\t\t\treturn false;\n+\t\t}\n+\t} else if (ack.memory_types) {\n+\t\t/* Like @spare the field must be zero when it isn't used. */\n+\t\tcoredump_sock_mark(cprm-\u003efile, COREDUMP_MARK_UNSUPPORTED);\n+\t\treturn false;\n+\t}\n+\n \t/* Record header scratch; a bvec can't point at the stack. */\n \tif (ack.mask \u0026 COREDUMP_RECORDS) {\n \t\tcprm-\u003erecord_hdr = kmalloc_obj(*cprm-\u003erecord_hdr);\n@@ -880,6 +936,10 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *\n \t\t\treturn false;\n \t}\n \n+\t/* The server's selection replaces the task's entirely. */\n+\tif (ack.mask \u0026 COREDUMP_MEMORY_TYPES)\n+\t\tcprm-\u003ememory_types = ack.memory_types;\n+\n \tcprm-\u003emask = ack.mask;\n \treturn coredump_sock_mark(cprm-\u003efile, COREDUMP_MARK_REQACK);\n }\n@@ -1200,6 +1260,10 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm,\n \t}\n }\n \n+#define COREDUMP_TASK_MEMORY_TYPES(mm) \\\n+\t((__mm_flags_get_word((mm)) \u0026 MMF_DUMP_FILTER_MASK) \u003e\u003e \\\n+\t MMF_DUMP_FILTER_SHIFT)\n+\n void vfs_coredump(const kernel_siginfo_t *siginfo)\n {\n \tsize_t *argv __free(kfree) = NULL;\n@@ -1211,8 +1275,8 @@ void vfs_coredump(const kernel_siginfo_t *siginfo)\n \tstruct coredump_params cprm = {\n \t\t.siginfo = siginfo,\n \t\t.limit = rlimit(RLIMIT_CORE),\n-\t\t/* Snapshot MMF_DUMP_FILTER_* (unlocked) and dumpable for the dump. */\n-\t\t.mm_flags = __mm_flags_get_word(mm),\n+\t\t/* Snapshot the memory types (unlocked) and dumpable for the dump. */\n+\t\t.memory_types = COREDUMP_TASK_MEMORY_TYPES(mm),\n \t\t.dumpable = task_exec_state_get_dumpable(current),\n \t\t.vma_meta = NULL,\n \t\t.cpu = raw_smp_processor_id(),\n@@ -1746,15 +1810,15 @@ static bool always_dump_vma(struct vm_area_struct *vma)\n }\n \n #define DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER 1\n+#define COREDUMP_MEMORY_TYPE_INCLUDE(types, type) \\\n+\t((types) \u0026 COREDUMP_MEMORY_##type)\n \n /*\n * Decide how much of @vma's contents should be included in a core dump.\n */\n static unsigned long vma_dump_size(struct vm_area_struct *vma,\n-\t\t\t\t unsigned long mm_flags)\n+\t\t\t\t u64 memory_types)\n {\n-#define FILTER(type)\t(mm_flags \u0026 (1UL \u003c\u003c MMF_DUMP_##type))\n-\n \t/* always dump the vdso and vsyscall sections */\n \tif (always_dump_vma(vma))\n \t\tgoto whole;\n@@ -1764,18 +1828,22 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,\n \n \t/* support for DAX */\n \tif (vma_is_dax(vma)) {\n-\t\tif ((vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026 FILTER(DAX_SHARED))\n+\t\tif ((vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026\n+\t\t COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, DAX_SHARED))\n \t\t\tgoto whole;\n-\t\tif (!(vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026 FILTER(DAX_PRIVATE))\n+\t\tif (!(vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026\n+\t\t COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, DAX_PRIVATE))\n \t\t\tgoto whole;\n \t\treturn 0;\n \t}\n \n \t/* Hugetlb memory check */\n \tif (is_vm_hugetlb_page(vma)) {\n-\t\tif ((vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026 FILTER(HUGETLB_SHARED))\n+\t\tif ((vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026\n+\t\t COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, HUGETLB_SHARED))\n \t\t\tgoto whole;\n-\t\tif (!(vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026 FILTER(HUGETLB_PRIVATE))\n+\t\tif (!(vma-\u003evm_flags \u0026 VM_SHARED) \u0026\u0026\n+\t\t COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, HUGETLB_PRIVATE))\n \t\t\tgoto whole;\n \t\treturn 0;\n \t}\n@@ -1787,25 +1855,27 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,\n \t/* By default, dump shared memory if mapped from an anonymous file. */\n \tif (vma-\u003evm_flags \u0026 VM_SHARED) {\n \t\tif (file_inode(vma-\u003evm_file)-\u003ei_nlink == 0 ?\n-\t\t FILTER(ANON_SHARED) : FILTER(MAPPED_SHARED))\n+\t\t\t COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ANON_SHARED) :\n+\t\t\t COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, FILE_SHARED))\n \t\t\tgoto whole;\n \t\treturn 0;\n \t}\n \n \t/* Dump segments that have been written to. */\n-\tif ((!IS_ENABLED(CONFIG_MMU) || vma-\u003eanon_vma) \u0026\u0026 FILTER(ANON_PRIVATE))\n+\tif ((!IS_ENABLED(CONFIG_MMU) || vma-\u003eanon_vma) \u0026\u0026\n+\t COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ANON_PRIVATE))\n \t\tgoto whole;\n \tif (vma-\u003evm_file == NULL)\n \t\treturn 0;\n \n-\tif (FILTER(MAPPED_PRIVATE))\n+\tif (COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, FILE_PRIVATE))\n \t\tgoto whole;\n \n \t/*\n \t * If this is the beginning of an executable file mapping,\n \t * dump the first page to aid in determining what was mapped here.\n \t */\n-\tif (FILTER(ELF_HEADERS) \u0026\u0026\n+\tif (COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ELF_HEADERS) \u0026\u0026\n \t vma-\u003evm_pgoff == 0 \u0026\u0026 (vma-\u003evm_flags \u0026 VM_READ)) {\n \t\tif ((READ_ONCE(file_inode(vma-\u003evm_file)-\u003ei_mode) \u0026 0111) != 0)\n \t\t\treturn PAGE_SIZE;\n@@ -1821,8 +1891,6 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,\n \t\treturn DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER;\n \t}\n \n-#undef\tFILTER\n-\n \treturn 0;\n \n whole:\n@@ -1907,7 +1975,7 @@ static bool dump_vma_snapshot(struct coredump_params *cprm)\n \t\tm-\u003estart = vma-\u003evm_start;\n \t\tm-\u003eend = vma-\u003evm_end;\n \t\tm-\u003eflags = vma-\u003evm_flags;\n-\t\tm-\u003edump_size = vma_dump_size(vma, cprm-\u003emm_flags);\n+\t\tm-\u003edump_size = vma_dump_size(vma, cprm-\u003ememory_types);\n \t\tm-\u003epgoff = vma-\u003evm_pgoff;\n \t\tm-\u003efile = vma-\u003evm_file;\n \t\tif (m-\u003efile)\ndiff --git a/include/linux/coredump.h b/include/linux/coredump.h\nindex b252bb2843b33..74af57b9406b8 100644\n--- a/include/linux/coredump.h\n+++ b/include/linux/coredump.h\n@@ -32,8 +32,8 @@ struct coredump_params {\n \tconst kernel_siginfo_t *siginfo;\n \tstruct file *file;\n \tunsigned long limit;\n-\t/* MMF_DUMP_FILTER_* bits, snapshot of mm-\u003eflags at dump start. */\n-\tunsigned long mm_flags;\n+\t/* COREDUMP_MEMORY_* types to dump, the task's or the server's. */\n+\tu64 memory_types;\n \t/* Snapshot of dumpable at dump start. */\n \tenum task_dumpable dumpable;\n \tint cpu;\ndiff --git a/include/uapi/linux/coredump.h b/include/uapi/linux/coredump.h\nindex f3771861ca485..6d0c53b534ea8 100644\n--- a/include/uapi/linux/coredump.h\n+++ b/include/uapi/linux/coredump.h\n@@ -16,6 +16,9 @@\n * requires COREDUMP_KERNEL\n * @COREDUMP_SPARSE: describe the holes in the coredump as zero records\n * instead of transferring them; requires COREDUMP_RECORDS\n+ * @COREDUMP_MEMORY_TYPES: dump the memory types in\n+ * coredump_ack-\u003ememory_types instead of the ones\n+ * the task selected; requires COREDUMP_KERNEL\n */\n enum {\n \tCOREDUMP_KERNEL\t\t= (1ULL \u003c\u003c 0),\n@@ -24,6 +27,37 @@ enum {\n \tCOREDUMP_WAIT\t\t= (1ULL \u003c\u003c 3),\n \tCOREDUMP_RECORDS\t= (1ULL \u003c\u003c 4),\n \tCOREDUMP_SPARSE\t\t= (1ULL \u003c\u003c 5),\n+\tCOREDUMP_MEMORY_TYPES\t= (1ULL \u003c\u003c 6),\n+};\n+\n+/**\n+ * coredump memory types\n+ * @COREDUMP_MEMORY_ANON_PRIVATE: anonymous private memory\n+ * @COREDUMP_MEMORY_ANON_SHARED: anonymous shared memory\n+ * @COREDUMP_MEMORY_FILE_PRIVATE: file-backed private memory\n+ * @COREDUMP_MEMORY_FILE_SHARED: file-backed shared memory\n+ * @COREDUMP_MEMORY_ELF_HEADERS: the first page of a file-backed private\n+ * mapping that starts an ELF file\n+ * @COREDUMP_MEMORY_HUGETLB_PRIVATE: hugetlb private memory\n+ * @COREDUMP_MEMORY_HUGETLB_SHARED: hugetlb shared memory\n+ * @COREDUMP_MEMORY_DAX_PRIVATE: DAX private memory\n+ * @COREDUMP_MEMORY_DAX_SHARED: DAX shared memory\n+ *\n+ * A bitmask of memory types a coredump may request to be included. New\n+ * memory type bits must ensure that they do not steal memory from an\n+ * existing one so a coredump server will continue to get the same\n+ * coredumps even if a new bit is introduced.\n+ */\n+enum {\n+\tCOREDUMP_MEMORY_ANON_PRIVATE\t= (1ULL \u003c\u003c 0),\n+\tCOREDUMP_MEMORY_ANON_SHARED\t= (1ULL \u003c\u003c 1),\n+\tCOREDUMP_MEMORY_FILE_PRIVATE\t= (1ULL \u003c\u003c 2),\n+\tCOREDUMP_MEMORY_FILE_SHARED\t= (1ULL \u003c\u003c 3),\n+\tCOREDUMP_MEMORY_ELF_HEADERS\t= (1ULL \u003c\u003c 4),\n+\tCOREDUMP_MEMORY_HUGETLB_PRIVATE\t= (1ULL \u003c\u003c 5),\n+\tCOREDUMP_MEMORY_HUGETLB_SHARED\t= (1ULL \u003c\u003c 6),\n+\tCOREDUMP_MEMORY_DAX_PRIVATE\t= (1ULL \u003c\u003c 7),\n+\tCOREDUMP_MEMORY_DAX_SHARED\t= (1ULL \u003c\u003c 8),\n };\n \n /**\n@@ -31,6 +65,8 @@ enum {\n * @size: size of struct coredump_req\n * @size_ack: known size of struct coredump_ack on this kernel\n * @mask: supported features\n+ * @memory_types: the memory types the task selected\n+ * @memory_types_mask: the memory types this kernel knows\n *\n * When a coredump happens the kernel will connect to the coredump\n * socket and send a coredump request to the coredump server. The @size\n@@ -49,15 +85,27 @@ enum {\n * struct coredump_ack the kernel knows. Userspace may only send up to\n * coredump_req-\u003esize_ack bytes to the kernel and must set\n * coredump_ack-\u003esize accordingly.\n+ *\n+ * @memory_types is set to the default memory types that are included in\n+ * the coredump. This can be overridden by raising bits in\n+ * coredump_ack-\u003ememory_types.\n+ *\n+ * @memory_types_mask contains a bitmask of all memory types the kernel\n+ * knows about. A coredump server may only raise bits in\n+ * coredump_ack-\u003ememory_types that are raised in\n+ * coredump_req-\u003ememory_types_mask.\n */\n struct coredump_req {\n \t__u32 size;\n \t__u32 size_ack;\n \t__u64 mask;\n+\t__u64 memory_types;\n+\t__u64 memory_types_mask;\n };\n \n enum {\n \tCOREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */\n+\tCOREDUMP_REQ_SIZE_VER1 = 32U, /* memory_types and memory_types_mask added */\n };\n \n /**\n@@ -65,6 +113,8 @@ enum {\n * @size: size of the struct\n * @spare: unused\n * @mask: features kernel is supposed to use\n+ * @memory_types: memory types to dump, only with COREDUMP_MEMORY_TYPES\n+ * in @mask\n *\n * The @size member must be set to the size of struct coredump_ack. It\n * may never exceed what the kernel returned in coredump_req-\u003esize_ack\n@@ -74,15 +124,30 @@ enum {\n * The @mask member must be set to the features the coredump server\n * wants the kernel to use. Only bits the kernel returned in\n * coredump_req-\u003emask may be set.\n+ *\n+ * If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the\n+ * memory types set in the @memory_types mask. Zero is valid and dumps\n+ * no memory apart from the mappings that are always dumped.\n+ *\n+ * Note that memory a task excluded via MADV_DONTDUMP is always left\n+ * out. A coredump server wanting to add or drop memory types instead of\n+ * outright replacing it should simply copy coredump_req-\u003ememory_types\n+ * and then mask off or raise types as needed.\n+ *\n+ * Note that @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't\n+ * raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of\n+ * at least COREDUMP_ACK_SIZE_VER1 bytes.\n */\n struct coredump_ack {\n \t__u32 size;\n \t__u32 spare;\n \t__u64 mask;\n+\t__u64 memory_types;\n };\n \n enum {\n \tCOREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */\n+\tCOREDUMP_ACK_SIZE_VER1 = 24U, /* memory_types added */\n };\n \n /**\n@@ -90,11 +155,12 @@ enum {\n *\n * The kernel will place a single byte on the coredump socket. The\n * markers notify userspace whether the coredump ack succeeded or\n- * failed.\n+ * failed. After any marker other than COREDUMP_MARK_REQACK the kernel\n+ * closes the connection and no coredump is generated.\n *\n * @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small\n * @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big\n- * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid\n+ * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask or memory types were invalid\n * @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options\n * @COREDUMP_MARK_REQACK: the coredump request and ack was successful\n * @__COREDUMP_MARK_MAX: the maximum coredump mark value\ndiff --git a/tools/include/uapi/linux/coredump.h b/tools/include/uapi/linux/coredump.h\nindex f3771861ca485..6d0c53b534ea8 100644\n--- a/tools/include/uapi/linux/coredump.h\n+++ b/tools/include/uapi/linux/coredump.h\n@@ -16,6 +16,9 @@\n * requires COREDUMP_KERNEL\n * @COREDUMP_SPARSE: describe the holes in the coredump as zero records\n * instead of transferring them; requires COREDUMP_RECORDS\n+ * @COREDUMP_MEMORY_TYPES: dump the memory types in\n+ * coredump_ack-\u003ememory_types instead of the ones\n+ * the task selected; requires COREDUMP_KERNEL\n */\n enum {\n \tCOREDUMP_KERNEL\t\t= (1ULL \u003c\u003c 0),\n@@ -24,6 +27,37 @@ enum {\n \tCOREDUMP_WAIT\t\t= (1ULL \u003c\u003c 3),\n \tCOREDUMP_RECORDS\t= (1ULL \u003c\u003c 4),\n \tCOREDUMP_SPARSE\t\t= (1ULL \u003c\u003c 5),\n+\tCOREDUMP_MEMORY_TYPES\t= (1ULL \u003c\u003c 6),\n+};\n+\n+/**\n+ * coredump memory types\n+ * @COREDUMP_MEMORY_ANON_PRIVATE: anonymous private memory\n+ * @COREDUMP_MEMORY_ANON_SHARED: anonymous shared memory\n+ * @COREDUMP_MEMORY_FILE_PRIVATE: file-backed private memory\n+ * @COREDUMP_MEMORY_FILE_SHARED: file-backed shared memory\n+ * @COREDUMP_MEMORY_ELF_HEADERS: the first page of a file-backed private\n+ * mapping that starts an ELF file\n+ * @COREDUMP_MEMORY_HUGETLB_PRIVATE: hugetlb private memory\n+ * @COREDUMP_MEMORY_HUGETLB_SHARED: hugetlb shared memory\n+ * @COREDUMP_MEMORY_DAX_PRIVATE: DAX private memory\n+ * @COREDUMP_MEMORY_DAX_SHARED: DAX shared memory\n+ *\n+ * A bitmask of memory types a coredump may request to be included. New\n+ * memory type bits must ensure that they do not steal memory from an\n+ * existing one so a coredump server will continue to get the same\n+ * coredumps even if a new bit is introduced.\n+ */\n+enum {\n+\tCOREDUMP_MEMORY_ANON_PRIVATE\t= (1ULL \u003c\u003c 0),\n+\tCOREDUMP_MEMORY_ANON_SHARED\t= (1ULL \u003c\u003c 1),\n+\tCOREDUMP_MEMORY_FILE_PRIVATE\t= (1ULL \u003c\u003c 2),\n+\tCOREDUMP_MEMORY_FILE_SHARED\t= (1ULL \u003c\u003c 3),\n+\tCOREDUMP_MEMORY_ELF_HEADERS\t= (1ULL \u003c\u003c 4),\n+\tCOREDUMP_MEMORY_HUGETLB_PRIVATE\t= (1ULL \u003c\u003c 5),\n+\tCOREDUMP_MEMORY_HUGETLB_SHARED\t= (1ULL \u003c\u003c 6),\n+\tCOREDUMP_MEMORY_DAX_PRIVATE\t= (1ULL \u003c\u003c 7),\n+\tCOREDUMP_MEMORY_DAX_SHARED\t= (1ULL \u003c\u003c 8),\n };\n \n /**\n@@ -31,6 +65,8 @@ enum {\n * @size: size of struct coredump_req\n * @size_ack: known size of struct coredump_ack on this kernel\n * @mask: supported features\n+ * @memory_types: the memory types the task selected\n+ * @memory_types_mask: the memory types this kernel knows\n *\n * When a coredump happens the kernel will connect to the coredump\n * socket and send a coredump request to the coredump server. The @size\n@@ -49,15 +85,27 @@ enum {\n * struct coredump_ack the kernel knows. Userspace may only send up to\n * coredump_req-\u003esize_ack bytes to the kernel and must set\n * coredump_ack-\u003esize accordingly.\n+ *\n+ * @memory_types is set to the default memory types that are included in\n+ * the coredump. This can be overridden by raising bits in\n+ * coredump_ack-\u003ememory_types.\n+ *\n+ * @memory_types_mask contains a bitmask of all memory types the kernel\n+ * knows about. A coredump server may only raise bits in\n+ * coredump_ack-\u003ememory_types that are raised in\n+ * coredump_req-\u003ememory_types_mask.\n */\n struct coredump_req {\n \t__u32 size;\n \t__u32 size_ack;\n \t__u64 mask;\n+\t__u64 memory_types;\n+\t__u64 memory_types_mask;\n };\n \n enum {\n \tCOREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */\n+\tCOREDUMP_REQ_SIZE_VER1 = 32U, /* memory_types and memory_types_mask added */\n };\n \n /**\n@@ -65,6 +113,8 @@ enum {\n * @size: size of the struct\n * @spare: unused\n * @mask: features kernel is supposed to use\n+ * @memory_types: memory types to dump, only with COREDUMP_MEMORY_TYPES\n+ * in @mask\n *\n * The @size member must be set to the size of struct coredump_ack. It\n * may never exceed what the kernel returned in coredump_req-\u003esize_ack\n@@ -74,15 +124,30 @@ enum {\n * The @mask member must be set to the features the coredump server\n * wants the kernel to use. Only bits the kernel returned in\n * coredump_req-\u003emask may be set.\n+ *\n+ * If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the\n+ * memory types set in the @memory_types mask. Zero is valid and dumps\n+ * no memory apart from the mappings that are always dumped.\n+ *\n+ * Note that memory a task excluded via MADV_DONTDUMP is always left\n+ * out. A coredump server wanting to add or drop memory types instead of\n+ * outright replacing it should simply copy coredump_req-\u003ememory_types\n+ * and then mask off or raise types as needed.\n+ *\n+ * Note that @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't\n+ * raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of\n+ * at least COREDUMP_ACK_SIZE_VER1 bytes.\n */\n struct coredump_ack {\n \t__u32 size;\n \t__u32 spare;\n \t__u64 mask;\n+\t__u64 memory_types;\n };\n \n enum {\n \tCOREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */\n+\tCOREDUMP_ACK_SIZE_VER1 = 24U, /* memory_types added */\n };\n \n /**\n@@ -90,11 +155,12 @@ enum {\n *\n * The kernel will place a single byte on the coredump socket. The\n * markers notify userspace whether the coredump ack succeeded or\n- * failed.\n+ * failed. After any marker other than COREDUMP_MARK_REQACK the kernel\n+ * closes the connection and no coredump is generated.\n *\n * @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small\n * @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big\n- * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid\n+ * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask or memory types were invalid\n * @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options\n * @COREDUMP_MARK_REQACK: the coredump request and ack was successful\n * @__COREDUMP_MARK_MAX: the maximum coredump mark value\ndiff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c\nindex daff908232a22..f5c9bad875463 100644\n--- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c\n+++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c\n@@ -508,91 +508,89 @@ TEST_F(coredump, socket_request_reject)\n \twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n }\n \n-TEST_F(coredump, socket_request_invalid_flag_combination)\n+/* An ack the kernel must refuse and how. */\n+struct refused_ack {\n+\t/* The ack, and how many bytes of it the server sends before it hangs up. */\n+\tstruct coredump_ack ack;\n+\tsize_t bytes;\n+\t/* The marker the kernel answers with, or none if @no_marker. */\n+\tenum coredump_mark mark;\n+\tbool no_marker;\n+};\n+\n+/* Send @refused, expect the kernel to refuse it and hang up. */\n+static void check_refused_ack(struct __test_metadata *const _metadata,\n+\t\t\t FIXTURE_DATA(coredump) *self,\n+\t\t\t const struct refused_ack *refused)\n {\n-\tint pidfd, ret, status;\n+\tint pidfd, status;\n \tpid_t pid, pid_coredump_server;\n \tstruct pidfd_info info = {};\n \tint ipc_sockets[2];\n \tchar c;\n \n+\tASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);\n \tASSERT_TRUE(set_core_pattern(\"@@/tmp/coredump.socket\"));\n \n-\tret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);\n-\tASSERT_EQ(ret, 0);\n-\n \tpid_coredump_server = fork();\n \tASSERT_GE(pid_coredump_server, 0);\n \tif (pid_coredump_server == 0) {\n-\t\tstruct coredump_req req = {};\n \t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;\n \t\tint exit_code = EXIT_FAILURE;\n+\t\tstruct coredump_req req = {};\n \n \t\tclose(ipc_sockets[0]);\n \n \t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n-\t\tif (fd_server \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: create_and_listen_unix_socket failed: %m\\n\");\n+\t\tif (fd_server \u003c 0)\n \t\t\tgoto out;\n-\t\t}\n \n-\t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: write_nointr to ipc socket failed: %m\\n\");\n+\t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0)\n \t\t\tgoto out;\n-\t\t}\n \n \t\tclose(ipc_sockets[1]);\n \n \t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n-\t\tif (fd_coredump \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: accept4 failed: %m\\n\");\n+\t\tif (fd_coredump \u003c 0)\n \t\t\tgoto out;\n-\t\t}\n \n \t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n-\t\tif (fd_peer_pidfd \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: get_peer_pidfd failed\\n\");\n+\t\tif (fd_peer_pidfd \u003c 0)\n \t\t\tgoto out;\n-\t\t}\n \n-\t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: get_pidfd_info failed\\n\");\n+\t\t/* The task shows as dumping while it waits for the ack. */\n+\t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info))\n \t\t\tgoto out;\n-\t\t}\n \n-\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: PIDFD_INFO_COREDUMP not set in mask\\n\");\n+\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP) ||\n+\t\t !(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n+\t\t\tfprintf(stderr, \"Peer isn't marked as dumping\\n\");\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (!(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: PIDFD_COREDUMPED not set in coredump_mask\\n\");\n+\t\tif (!read_coredump_req(fd_coredump, \u0026req))\n \t\t\tgoto out;\n-\t\t}\n \n-\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: read_coredump_req failed\\n\");\n+\t\tif (!check_coredump_req(\u0026req))\n \t\t\tgoto out;\n-\t\t}\n \n-\t\tif (!check_coredump_req(\u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: check_coredump_req failed\\n\");\n+\t\tif (!send_coredump_ack_bytes(fd_coredump, \u0026refused-\u003eack,\n+\t\t\t\t\t refused-\u003ebytes))\n \t\t\tgoto out;\n-\t\t}\n \n-\t\tif (!send_coredump_ack(fd_coredump, \u0026req,\n-\t\t\t\t COREDUMP_KERNEL | COREDUMP_REJECT | COREDUMP_WAIT, 0)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: send_coredump_ack failed\\n\");\n+\t\t/* Nothing more to say. A server that died looks the same. */\n+\t\tif (shutdown(fd_coredump, SHUT_WR))\n \t\t\tgoto out;\n-\t\t}\n \n-\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: read_marker COREDUMP_MARK_CONFLICTING failed\\n\");\n+\t\tif (!refused-\u003eno_marker \u0026\u0026\n+\t\t !read_marker(fd_coredump, refused-\u003emark))\n+\t\t\tgoto out;\n+\n+\t\t/* The kernel hangs up after a refusal, marker or not. */\n+\t\tif (!read_hangup(fd_coredump))\n \t\t\tgoto out;\n-\t\t}\n \n \t\texit_code = EXIT_SUCCESS;\n-\t\tfprintf(stderr, \"socket_request_invalid_flag_combination: completed successfully\\n\");\n out:\n \t\tif (fd_peer_pidfd \u003e= 0)\n \t\t\tclose(fd_peer_pidfd);\n@@ -627,7 +625,83 @@ TEST_F(coredump, socket_request_invalid_flag_combination)\n \twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n }\n \n+/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */\n+static void check_conflicting_ack(struct __test_metadata *const _metadata,\n+\t\t\t\t FIXTURE_DATA(coredump) *self, __u64 ack_mask)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = sizeof(struct coredump_ack),\n+\t\t\t.mask = ack_mask,\n+\t\t},\n+\t\t.bytes = sizeof(struct coredump_ack),\n+\t\t.mark = COREDUMP_MARK_CONFLICTING,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* More than one of KERNEL, USERSPACE and REJECT. */\n+TEST_F(coredump, socket_request_invalid_flag_combination)\n+{\n+\tcheck_conflicting_ack(_metadata, self,\n+\t\t\t COREDUMP_KERNEL | COREDUMP_REJECT | COREDUMP_WAIT);\n+}\n+\n+/* A flag the kernel didn't advertise in coredump_req-\u003emask. */\n TEST_F(coredump, socket_request_unknown_flag)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = sizeof(struct coredump_ack),\n+\t\t\t.mask = 1ULL \u003c\u003c 63,\n+\t\t},\n+\t\t.bytes = sizeof(struct coredump_ack),\n+\t\t.mark = COREDUMP_MARK_UNSUPPORTED,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* An ack smaller than the first published struct. */\n+TEST_F(coredump, socket_request_invalid_size_small)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = COREDUMP_ACK_SIZE_VER0 / 2,\n+\t\t\t.mask = COREDUMP_REJECT | COREDUMP_WAIT,\n+\t\t},\n+\t\t.bytes = COREDUMP_ACK_SIZE_VER0 / 2,\n+\t\t.mark = COREDUMP_MARK_MINSIZE,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* An ack bigger than the kernel said it accepts. */\n+TEST_F(coredump, socket_request_invalid_size_large)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE,\n+\t\t\t.mask = COREDUMP_REJECT | COREDUMP_WAIT,\n+\t\t},\n+\t\t.bytes = COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE,\n+\t\t.mark = COREDUMP_MARK_MAXSIZE,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/*\n+ * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGSEGV\n+ *\n+ * Verify that when using socket-based coredump protocol,\n+ * the coredump_signal field is correctly exposed as SIGSEGV.\n+ * Also check that the coredump_code field is correctly exposed\n+ * as SEGV_MAPERR.\n+ */\n+TEST_F(coredump, socket_coredump_signal_sigsegv)\n {\n \tint pidfd, ret, status;\n \tpid_t pid, pid_coredump_server;\n@@ -651,12 +725,12 @@ TEST_F(coredump, socket_request_unknown_flag)\n \n \t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n \t\tif (fd_server \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: create_and_listen_unix_socket failed: %m\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: create_and_listen_unix_socket failed: %m\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: write_nointr to ipc socket failed: %m\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: write_nointr to ipc socket failed: %m\\n\");\n \t\t\tgoto out;\n \t\t}\n \n@@ -664,53 +738,73 @@ TEST_F(coredump, socket_request_unknown_flag)\n \n \t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n \t\tif (fd_coredump \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: accept4 failed: %m\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: accept4 failed: %m\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n \t\tif (fd_peer_pidfd \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: get_peer_pidfd failed\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: get_peer_pidfd failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info)) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: get_pidfd_info failed\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: get_pidfd_info failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP)) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: PIDFD_INFO_COREDUMP not set in mask\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP not set in mask\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (!(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: PIDFD_COREDUMPED not set in coredump_mask\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: PIDFD_COREDUMPED not set in coredump_mask\\n\");\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: read_coredump_req failed\\n\");\n+\t\t/* Verify coredump_signal is available and correct */\n+\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP_SIGNAL)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\\n\");\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (!check_coredump_req(\u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: check_coredump_req failed\\n\");\n+\t\tif (info.coredump_signal != SIGSEGV) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: coredump_signal=%d, expected SIGSEGV=%d\\n\",\n+\t\t\t\tinfo.coredump_signal, SIGSEGV);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/* Verify coredump_code is available and correct */\n+\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP_CODE)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_CODE not set in mask\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (info.coredump_code != SEGV_MAPERR) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: coredump_code=%d, expected SEGV_MAPERR=%d\\n\",\n+\t\t\t\tinfo.coredump_code, SEGV_MAPERR);\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (!send_coredump_ack(fd_coredump, \u0026req, (1ULL \u003c\u003c 63), 0)) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: send_coredump_ack failed\\n\");\n+\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: read_coredump_req failed\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (!send_coredump_ack(fd_coredump, \u0026req,\n+\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT, 0)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: send_coredump_ack failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_UNSUPPORTED)) {\n-\t\t\tfprintf(stderr, \"socket_request_unknown_flag: read_marker COREDUMP_MARK_UNSUPPORTED failed\\n\");\n+\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: read_marker COREDUMP_MARK_REQACK failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\texit_code = EXIT_SUCCESS;\n-\t\tfprintf(stderr, \"socket_request_unknown_flag: completed successfully\\n\");\n+\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: completed successfully\\n\");\n out:\n \t\tif (fd_peer_pidfd \u003e= 0)\n \t\t\tclose(fd_peer_pidfd);\n@@ -736,16 +830,27 @@ TEST_F(coredump, socket_request_unknown_flag)\n \n \twaitpid(pid, \u0026status, 0);\n \tASSERT_TRUE(WIFSIGNALED(status));\n-\tASSERT_FALSE(WCOREDUMP(status));\n+\tASSERT_EQ(WTERMSIG(status), SIGSEGV);\n \n \tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n-\tASSERT_GT((info.mask \u0026 PIDFD_INFO_COREDUMP), 0);\n-\tASSERT_GT((info.coredump_mask \u0026 PIDFD_COREDUMPED), 0);\n+\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP));\n+\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP_SIGNAL));\n+\tASSERT_EQ(info.coredump_signal, SIGSEGV);\n+\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP_CODE));\n+\tASSERT_EQ(info.coredump_code, SEGV_MAPERR);\n \n \twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n }\n \n-TEST_F(coredump, socket_request_invalid_size_small)\n+/*\n+ * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGABRT\n+ *\n+ * Verify that when using socket-based coredump protocol,\n+ * the coredump_signal field is correctly exposed as SIGABRT.\n+ * Also check that the coredump_code field is correctly exposed\n+ * as SI_TKILL.\n+ */\n+TEST_F(coredump, socket_coredump_signal_sigabrt)\n {\n \tint pidfd, ret, status;\n \tpid_t pid, pid_coredump_server;\n@@ -769,12 +874,12 @@ TEST_F(coredump, socket_request_invalid_size_small)\n \n \t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n \t\tif (fd_server \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: create_and_listen_unix_socket failed: %m\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: create_and_listen_unix_socket failed: %m\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: write_nointr to ipc socket failed: %m\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: write_nointr to ipc socket failed: %m\\n\");\n \t\t\tgoto out;\n \t\t}\n \n@@ -782,55 +887,67 @@ TEST_F(coredump, socket_request_invalid_size_small)\n \n \t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n \t\tif (fd_coredump \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: accept4 failed: %m\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: accept4 failed: %m\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n \t\tif (fd_peer_pidfd \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: get_peer_pidfd failed\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: get_peer_pidfd failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: get_pidfd_info failed\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: get_pidfd_info failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: PIDFD_INFO_COREDUMP not set in mask\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP not set in mask\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (!(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: PIDFD_COREDUMPED not set in coredump_mask\\n\");\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: PIDFD_COREDUMPED not set in coredump_mask\\n\");\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: read_coredump_req failed\\n\");\n+\t\t/* Verify coredump_signal is available and correct */\n+\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP_SIGNAL)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\\n\");\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (!check_coredump_req(\u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: check_coredump_req failed\\n\");\n+\t\tif (info.coredump_signal != SIGABRT) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: coredump_signal=%d, expected SIGABRT=%d\\n\",\n+\t\t\t\tinfo.coredump_signal, SIGABRT);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (info.coredump_code != SI_TKILL) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: coredump_code=%d, expected SI_TKILL=%d\\n\",\n+\t\t\t\tinfo.coredump_code, SI_TKILL);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: read_coredump_req failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (!send_coredump_ack(fd_coredump, \u0026req,\n-\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT,\n-\t\t\t\t COREDUMP_ACK_SIZE_VER0 / 2)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: send_coredump_ack failed\\n\");\n+\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT, 0)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: send_coredump_ack failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n-\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_MINSIZE)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_small: read_marker COREDUMP_MARK_MINSIZE failed\\n\");\n+\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: read_marker COREDUMP_MARK_REQACK failed\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\texit_code = EXIT_SUCCESS;\n-\t\tfprintf(stderr, \"socket_request_invalid_size_small: completed successfully\\n\");\n+\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: completed successfully\\n\");\n out:\n \t\tif (fd_peer_pidfd \u003e= 0)\n \t\t\tclose(fd_peer_pidfd);\n@@ -849,513 +966,104 @@ TEST_F(coredump, socket_request_invalid_size_small)\n \tpid = fork();\n \tASSERT_GE(pid, 0);\n \tif (pid == 0)\n-\t\tcrashing_child();\n+\t\tabort();\n \n \tpidfd = sys_pidfd_open(pid, 0);\n \tASSERT_GE(pidfd, 0);\n \n \twaitpid(pid, \u0026status, 0);\n \tASSERT_TRUE(WIFSIGNALED(status));\n-\tASSERT_FALSE(WCOREDUMP(status));\n+\tASSERT_EQ(WTERMSIG(status), SIGABRT);\n \n \tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n-\tASSERT_GT((info.mask \u0026 PIDFD_INFO_COREDUMP), 0);\n-\tASSERT_GT((info.coredump_mask \u0026 PIDFD_COREDUMPED), 0);\n+\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP));\n+\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP_SIGNAL));\n+\tASSERT_EQ(info.coredump_signal, SIGABRT);\n+\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP_CODE));\n+\tASSERT_EQ(info.coredump_code, SI_TKILL);\n \n \twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n }\n \n-TEST_F(coredump, socket_request_invalid_size_large)\n+TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps, 500)\n {\n-\tint pidfd, ret, status;\n-\tpid_t pid, pid_coredump_server;\n+\tint pidfd[NUM_CRASHING_COREDUMPS], status[NUM_CRASHING_COREDUMPS];\n+\tpid_t pid[NUM_CRASHING_COREDUMPS], pid_coredump_server;\n \tstruct pidfd_info info = {};\n \tint ipc_sockets[2];\n \tchar c;\n \n \tASSERT_TRUE(set_core_pattern(\"@@/tmp/coredump.socket\"));\n \n-\tret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);\n-\tASSERT_EQ(ret, 0);\n+\tASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);\n \n \tpid_coredump_server = fork();\n \tASSERT_GE(pid_coredump_server, 0);\n \tif (pid_coredump_server == 0) {\n-\t\tstruct coredump_req req = {};\n-\t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;\n+\t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;\n \t\tint exit_code = EXIT_FAILURE;\n+\t\tstruct coredump_req req = {};\n \n \t\tclose(ipc_sockets[0]);\n-\n \t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n \t\tif (fd_server \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: create_and_listen_unix_socket failed: %m\\n\");\n+\t\t\tfprintf(stderr, \"Failed to create and listen on unix socket\\n\");\n \t\t\tgoto out;\n \t\t}\n \n \t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: write_nointr to ipc socket failed: %m\\n\");\n+\t\t\tfprintf(stderr, \"Failed to notify parent via ipc socket\\n\");\n \t\t\tgoto out;\n \t\t}\n-\n \t\tclose(ipc_sockets[1]);\n \n-\t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n-\t\tif (fd_coredump \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: accept4 failed: %m\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n-\t\tif (fd_peer_pidfd \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: get_peer_pidfd failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n+\t\tfor (int i = 0; i \u003c NUM_CRASHING_COREDUMPS; i++) {\n+\t\t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n+\t\t\tif (fd_coredump \u003c 0) {\n+\t\t\t\tfprintf(stderr, \"accept4 failed: %m\\n\");\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n-\t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: get_pidfd_info failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n+\t\t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n+\t\t\tif (fd_peer_pidfd \u003c 0) {\n+\t\t\t\tfprintf(stderr, \"get_peer_pidfd failed for fd %d: %m\\n\", fd_coredump);\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n-\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: PIDFD_INFO_COREDUMP not set in mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n+\t\t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info)) {\n+\t\t\t\tfprintf(stderr, \"get_pidfd_info failed for fd %d\\n\", fd_peer_pidfd);\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n-\t\tif (!(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: PIDFD_COREDUMPED not set in coredump_mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n+\t\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP)) {\n+\t\t\t\tfprintf(stderr, \"pidfd info missing PIDFD_INFO_COREDUMP for fd %d\\n\", fd_peer_pidfd);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tif (!(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n+\t\t\t\tfprintf(stderr, \"pidfd info missing PIDFD_COREDUMPED for fd %d\\n\", fd_peer_pidfd);\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n-\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: read_coredump_req failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n+\t\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n+\t\t\t\tfprintf(stderr, \"read_coredump_req failed for fd %d\\n\", fd_coredump);\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n-\t\tif (!check_coredump_req(\u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: check_coredump_req failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n+\t\t\tif (!check_coredump_req(\u0026req)) {\n+\t\t\t\tfprintf(stderr, \"check_coredump_req failed for fd %d\\n\", fd_coredump);\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n-\t\tif (!send_coredump_ack(fd_coredump, \u0026req,\n-\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT,\n-\t\t\t\t COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: send_coredump_ack failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n+\t\t\tif (!send_coredump_ack(fd_coredump, \u0026req,\n+\t\t\t\t\t COREDUMP_KERNEL | COREDUMP_WAIT, 0)) {\n+\t\t\t\tfprintf(stderr, \"send_coredump_ack failed for fd %d\\n\", fd_coredump);\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n-\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_MAXSIZE)) {\n-\t\t\tfprintf(stderr, \"socket_request_invalid_size_large: read_marker COREDUMP_MARK_MAXSIZE failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\texit_code = EXIT_SUCCESS;\n-\t\tfprintf(stderr, \"socket_request_invalid_size_large: completed successfully\\n\");\n-out:\n-\t\tif (fd_peer_pidfd \u003e= 0)\n-\t\t\tclose(fd_peer_pidfd);\n-\t\tif (fd_coredump \u003e= 0)\n-\t\t\tclose(fd_coredump);\n-\t\tif (fd_server \u003e= 0)\n-\t\t\tclose(fd_server);\n-\t\t_exit(exit_code);\n-\t}\n-\tself-\u003epid_coredump_server = pid_coredump_server;\n-\n-\tEXPECT_EQ(close(ipc_sockets[1]), 0);\n-\tASSERT_EQ(read_nointr(ipc_sockets[0], \u0026c, 1), 1);\n-\tEXPECT_EQ(close(ipc_sockets[0]), 0);\n-\n-\tpid = fork();\n-\tASSERT_GE(pid, 0);\n-\tif (pid == 0)\n-\t\tcrashing_child();\n-\n-\tpidfd = sys_pidfd_open(pid, 0);\n-\tASSERT_GE(pidfd, 0);\n-\n-\twaitpid(pid, \u0026status, 0);\n-\tASSERT_TRUE(WIFSIGNALED(status));\n-\tASSERT_FALSE(WCOREDUMP(status));\n-\n-\tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n-\tASSERT_GT((info.mask \u0026 PIDFD_INFO_COREDUMP), 0);\n-\tASSERT_GT((info.coredump_mask \u0026 PIDFD_COREDUMPED), 0);\n-\n-\twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n-}\n-\n-/*\n- * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGSEGV\n- *\n- * Verify that when using socket-based coredump protocol,\n- * the coredump_signal field is correctly exposed as SIGSEGV.\n- * Also check that the coredump_code field is correctly exposed\n- * as SEGV_MAPERR.\n- */\n-TEST_F(coredump, socket_coredump_signal_sigsegv)\n-{\n-\tint pidfd, ret, status;\n-\tpid_t pid, pid_coredump_server;\n-\tstruct pidfd_info info = {};\n-\tint ipc_sockets[2];\n-\tchar c;\n-\n-\tASSERT_TRUE(set_core_pattern(\"@@/tmp/coredump.socket\"));\n-\n-\tret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);\n-\tASSERT_EQ(ret, 0);\n-\n-\tpid_coredump_server = fork();\n-\tASSERT_GE(pid_coredump_server, 0);\n-\tif (pid_coredump_server == 0) {\n-\t\tstruct coredump_req req = {};\n-\t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;\n-\t\tint exit_code = EXIT_FAILURE;\n-\n-\t\tclose(ipc_sockets[0]);\n-\n-\t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n-\t\tif (fd_server \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: create_and_listen_unix_socket failed: %m\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: write_nointr to ipc socket failed: %m\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tclose(ipc_sockets[1]);\n-\n-\t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n-\t\tif (fd_coredump \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: accept4 failed: %m\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n-\t\tif (fd_peer_pidfd \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: get_peer_pidfd failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: get_pidfd_info failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP not set in mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: PIDFD_COREDUMPED not set in coredump_mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\t/* Verify coredump_signal is available and correct */\n-\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP_SIGNAL)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (info.coredump_signal != SIGSEGV) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: coredump_signal=%d, expected SIGSEGV=%d\\n\",\n-\t\t\t\tinfo.coredump_signal, SIGSEGV);\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\t/* Verify coredump_code is available and correct */\n-\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP_CODE)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_CODE not set in mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (info.coredump_code != SEGV_MAPERR) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: coredump_code=%d, expected SEGV_MAPERR=%d\\n\",\n-\t\t\t\tinfo.coredump_code, SEGV_MAPERR);\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: read_coredump_req failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!send_coredump_ack(fd_coredump, \u0026req,\n-\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT, 0)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: send_coredump_ack failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: read_marker COREDUMP_MARK_REQACK failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\texit_code = EXIT_SUCCESS;\n-\t\tfprintf(stderr, \"socket_coredump_signal_sigsegv: completed successfully\\n\");\n-out:\n-\t\tif (fd_peer_pidfd \u003e= 0)\n-\t\t\tclose(fd_peer_pidfd);\n-\t\tif (fd_coredump \u003e= 0)\n-\t\t\tclose(fd_coredump);\n-\t\tif (fd_server \u003e= 0)\n-\t\t\tclose(fd_server);\n-\t\t_exit(exit_code);\n-\t}\n-\tself-\u003epid_coredump_server = pid_coredump_server;\n-\n-\tEXPECT_EQ(close(ipc_sockets[1]), 0);\n-\tASSERT_EQ(read_nointr(ipc_sockets[0], \u0026c, 1), 1);\n-\tEXPECT_EQ(close(ipc_sockets[0]), 0);\n-\n-\tpid = fork();\n-\tASSERT_GE(pid, 0);\n-\tif (pid == 0)\n-\t\tcrashing_child();\n-\n-\tpidfd = sys_pidfd_open(pid, 0);\n-\tASSERT_GE(pidfd, 0);\n-\n-\twaitpid(pid, \u0026status, 0);\n-\tASSERT_TRUE(WIFSIGNALED(status));\n-\tASSERT_EQ(WTERMSIG(status), SIGSEGV);\n-\n-\tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n-\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP));\n-\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP_SIGNAL));\n-\tASSERT_EQ(info.coredump_signal, SIGSEGV);\n-\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP_CODE));\n-\tASSERT_EQ(info.coredump_code, SEGV_MAPERR);\n-\n-\twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n-}\n-\n-/*\n- * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGABRT\n- *\n- * Verify that when using socket-based coredump protocol,\n- * the coredump_signal field is correctly exposed as SIGABRT.\n- * Also check that the coredump_code field is correctly exposed\n- * as SI_TKILL.\n- */\n-TEST_F(coredump, socket_coredump_signal_sigabrt)\n-{\n-\tint pidfd, ret, status;\n-\tpid_t pid, pid_coredump_server;\n-\tstruct pidfd_info info = {};\n-\tint ipc_sockets[2];\n-\tchar c;\n-\n-\tASSERT_TRUE(set_core_pattern(\"@@/tmp/coredump.socket\"));\n-\n-\tret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);\n-\tASSERT_EQ(ret, 0);\n-\n-\tpid_coredump_server = fork();\n-\tASSERT_GE(pid_coredump_server, 0);\n-\tif (pid_coredump_server == 0) {\n-\t\tstruct coredump_req req = {};\n-\t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;\n-\t\tint exit_code = EXIT_FAILURE;\n-\n-\t\tclose(ipc_sockets[0]);\n-\n-\t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n-\t\tif (fd_server \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: create_and_listen_unix_socket failed: %m\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: write_nointr to ipc socket failed: %m\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tclose(ipc_sockets[1]);\n-\n-\t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n-\t\tif (fd_coredump \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: accept4 failed: %m\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n-\t\tif (fd_peer_pidfd \u003c 0) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: get_peer_pidfd failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: get_pidfd_info failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP not set in mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: PIDFD_COREDUMPED not set in coredump_mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\t/* Verify coredump_signal is available and correct */\n-\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP_SIGNAL)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (info.coredump_signal != SIGABRT) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: coredump_signal=%d, expected SIGABRT=%d\\n\",\n-\t\t\t\tinfo.coredump_signal, SIGABRT);\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (info.coredump_code != SI_TKILL) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: coredump_code=%d, expected SI_TKILL=%d\\n\",\n-\t\t\t\tinfo.coredump_code, SI_TKILL);\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: read_coredump_req failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!send_coredump_ack(fd_coredump, \u0026req,\n-\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT, 0)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: send_coredump_ack failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {\n-\t\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: read_marker COREDUMP_MARK_REQACK failed\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\texit_code = EXIT_SUCCESS;\n-\t\tfprintf(stderr, \"socket_coredump_signal_sigabrt: completed successfully\\n\");\n-out:\n-\t\tif (fd_peer_pidfd \u003e= 0)\n-\t\t\tclose(fd_peer_pidfd);\n-\t\tif (fd_coredump \u003e= 0)\n-\t\t\tclose(fd_coredump);\n-\t\tif (fd_server \u003e= 0)\n-\t\t\tclose(fd_server);\n-\t\t_exit(exit_code);\n-\t}\n-\tself-\u003epid_coredump_server = pid_coredump_server;\n-\n-\tEXPECT_EQ(close(ipc_sockets[1]), 0);\n-\tASSERT_EQ(read_nointr(ipc_sockets[0], \u0026c, 1), 1);\n-\tEXPECT_EQ(close(ipc_sockets[0]), 0);\n-\n-\tpid = fork();\n-\tASSERT_GE(pid, 0);\n-\tif (pid == 0)\n-\t\tabort();\n-\n-\tpidfd = sys_pidfd_open(pid, 0);\n-\tASSERT_GE(pidfd, 0);\n-\n-\twaitpid(pid, \u0026status, 0);\n-\tASSERT_TRUE(WIFSIGNALED(status));\n-\tASSERT_EQ(WTERMSIG(status), SIGABRT);\n-\n-\tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n-\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP));\n-\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP_SIGNAL));\n-\tASSERT_EQ(info.coredump_signal, SIGABRT);\n-\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP_CODE));\n-\tASSERT_EQ(info.coredump_code, SI_TKILL);\n-\n-\twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n-}\n-\n-TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps, 500)\n-{\n-\tint pidfd[NUM_CRASHING_COREDUMPS], status[NUM_CRASHING_COREDUMPS];\n-\tpid_t pid[NUM_CRASHING_COREDUMPS], pid_coredump_server;\n-\tstruct pidfd_info info = {};\n-\tint ipc_sockets[2];\n-\tchar c;\n-\n-\tASSERT_TRUE(set_core_pattern(\"@@/tmp/coredump.socket\"));\n-\n-\tASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);\n-\n-\tpid_coredump_server = fork();\n-\tASSERT_GE(pid_coredump_server, 0);\n-\tif (pid_coredump_server == 0) {\n-\t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;\n-\t\tint exit_code = EXIT_FAILURE;\n-\t\tstruct coredump_req req = {};\n-\n-\t\tclose(ipc_sockets[0]);\n-\t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n-\t\tif (fd_server \u003c 0) {\n-\t\t\tfprintf(stderr, \"Failed to create and listen on unix socket\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0) {\n-\t\t\tfprintf(stderr, \"Failed to notify parent via ipc socket\\n\");\n-\t\t\tgoto out;\n-\t\t}\n-\t\tclose(ipc_sockets[1]);\n-\n-\t\tfor (int i = 0; i \u003c NUM_CRASHING_COREDUMPS; i++) {\n-\t\t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n-\t\t\tif (fd_coredump \u003c 0) {\n-\t\t\t\tfprintf(stderr, \"accept4 failed: %m\\n\");\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n-\t\t\tif (fd_peer_pidfd \u003c 0) {\n-\t\t\t\tfprintf(stderr, \"get_peer_pidfd failed for fd %d: %m\\n\", fd_coredump);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info)) {\n-\t\t\t\tfprintf(stderr, \"get_pidfd_info failed for fd %d\\n\", fd_peer_pidfd);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tif (!(info.mask \u0026 PIDFD_INFO_COREDUMP)) {\n-\t\t\t\tfprintf(stderr, \"pidfd info missing PIDFD_INFO_COREDUMP for fd %d\\n\", fd_peer_pidfd);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t\tif (!(info.coredump_mask \u0026 PIDFD_COREDUMPED)) {\n-\t\t\t\tfprintf(stderr, \"pidfd info missing PIDFD_COREDUMPED for fd %d\\n\", fd_peer_pidfd);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tif (!read_coredump_req(fd_coredump, \u0026req)) {\n-\t\t\t\tfprintf(stderr, \"read_coredump_req failed for fd %d\\n\", fd_coredump);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tif (!check_coredump_req(\u0026req)) {\n-\t\t\t\tfprintf(stderr, \"check_coredump_req failed for fd %d\\n\", fd_coredump);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tif (!send_coredump_ack(fd_coredump, \u0026req,\n-\t\t\t\t\t COREDUMP_KERNEL | COREDUMP_WAIT, 0)) {\n-\t\t\t\tfprintf(stderr, \"send_coredump_ack failed for fd %d\\n\", fd_coredump);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {\n-\t\t\t\tfprintf(stderr, \"read_marker failed for fd %d\\n\", fd_coredump);\n-\t\t\t\tgoto out;\n-\t\t\t}\n+\t\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {\n+\t\t\t\tfprintf(stderr, \"read_marker failed for fd %d\\n\", fd_coredump);\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n \t\t\tfd_core_file = open_coredump_tmpfile(self-\u003efd_tmpfs_detached);\n \t\t\tif (fd_core_file \u003c 0) {\n@@ -2015,113 +1723,27 @@ TEST_F(coredump, socket_request_sparse_blob_upload)\n \tASSERT_EQ(read_nointr(pipefds[0], \u0026received, sizeof(received)),\n \t\t sizeof(received));\n \tASSERT_EQ(read_nointr(pipefds[0], \u0026coredump_size, sizeof(coredump_size)),\n-\t\t sizeof(coredump_size));\n-\tEXPECT_EQ(close(pipefds[0]), 0);\n-\n-\tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n-\tASSERT_GT((info.mask \u0026 PIDFD_INFO_COREDUMP), 0);\n-\tASSERT_GT((info.coredump_mask \u0026 PIDFD_COREDUMPED), 0);\n-\n-\twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n-\n-\t/* The mapping is in the coredump, holes included. */\n-\tASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE);\n-\n-\t/* The object isn't sparse and doesn't carry them. */\n-\tASSERT_EQ(stat(\"/tmp/coredump.file\", \u0026st), 0);\n-\tASSERT_LT(st.st_size, coredump_size / 8);\n-\n-\t/* And a debugger still sees an ordinary ELF core file. */\n-\tfd_core_file = open(\"/tmp/coredump.file\", O_RDONLY | O_CLOEXEC);\n-\tASSERT_GE(fd_core_file, 0);\n-\tASSERT_TRUE(is_elf_core(fd_core_file));\n-\tEXPECT_EQ(close(fd_core_file), 0);\n-}\n-\n-/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */\n-static void check_conflicting_ack(struct __test_metadata *const _metadata,\n-\t\t\t\t FIXTURE_DATA(coredump) *self, __u64 ack_mask)\n-{\n-\tint pidfd, status;\n-\tpid_t pid, pid_coredump_server;\n-\tstruct pidfd_info info = {};\n-\tint ipc_sockets[2];\n-\tchar c;\n-\n-\tASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);\n-\tASSERT_TRUE(set_core_pattern(\"@@/tmp/coredump.socket\"));\n-\n-\tpid_coredump_server = fork();\n-\tASSERT_GE(pid_coredump_server, 0);\n-\tif (pid_coredump_server == 0) {\n-\t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;\n-\t\tint exit_code = EXIT_FAILURE;\n-\t\tstruct coredump_req req = {};\n-\n-\t\tclose(ipc_sockets[0]);\n-\n-\t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n-\t\tif (fd_server \u003c 0)\n-\t\t\tgoto out;\n-\n-\t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0)\n-\t\t\tgoto out;\n-\n-\t\tclose(ipc_sockets[1]);\n-\n-\t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n-\t\tif (fd_coredump \u003c 0)\n-\t\t\tgoto out;\n-\n-\t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n-\t\tif (fd_peer_pidfd \u003c 0)\n-\t\t\tgoto out;\n-\n-\t\tif (!read_coredump_req(fd_coredump, \u0026req))\n-\t\t\tgoto out;\n-\n-\t\tif (!check_coredump_req(\u0026req))\n-\t\t\tgoto out;\n-\n-\t\tif (!send_coredump_ack(fd_coredump, \u0026req, ack_mask, 0))\n-\t\t\tgoto out;\n-\n-\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING))\n-\t\t\tgoto out;\n-\n-\t\texit_code = EXIT_SUCCESS;\n-out:\n-\t\tif (fd_peer_pidfd \u003e= 0)\n-\t\t\tclose(fd_peer_pidfd);\n-\t\tif (fd_coredump \u003e= 0)\n-\t\t\tclose(fd_coredump);\n-\t\tif (fd_server \u003e= 0)\n-\t\t\tclose(fd_server);\n-\t\t_exit(exit_code);\n-\t}\n-\tself-\u003epid_coredump_server = pid_coredump_server;\n-\n-\tEXPECT_EQ(close(ipc_sockets[1]), 0);\n-\tASSERT_EQ(read_nointr(ipc_sockets[0], \u0026c, 1), 1);\n-\tEXPECT_EQ(close(ipc_sockets[0]), 0);\n-\n-\tpid = fork();\n-\tASSERT_GE(pid, 0);\n-\tif (pid == 0)\n-\t\tcrashing_child();\n-\n-\tpidfd = sys_pidfd_open(pid, 0);\n-\tASSERT_GE(pidfd, 0);\n-\n-\twaitpid(pid, \u0026status, 0);\n-\tASSERT_TRUE(WIFSIGNALED(status));\n-\tASSERT_FALSE(WCOREDUMP(status));\n+\t\t sizeof(coredump_size));\n+\tEXPECT_EQ(close(pipefds[0]), 0);\n \n \tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n \tASSERT_GT((info.mask \u0026 PIDFD_INFO_COREDUMP), 0);\n \tASSERT_GT((info.coredump_mask \u0026 PIDFD_COREDUMPED), 0);\n \n \twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n+\n+\t/* The mapping is in the coredump, holes included. */\n+\tASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE);\n+\n+\t/* The object isn't sparse and doesn't carry them. */\n+\tASSERT_EQ(stat(\"/tmp/coredump.file\", \u0026st), 0);\n+\tASSERT_LT(st.st_size, coredump_size / 8);\n+\n+\t/* And a debugger still sees an ordinary ELF core file. */\n+\tfd_core_file = open(\"/tmp/coredump.file\", O_RDONLY | O_CLOEXEC);\n+\tASSERT_GE(fd_core_file, 0);\n+\tASSERT_TRUE(is_elf_core(fd_core_file));\n+\tEXPECT_EQ(close(fd_core_file), 0);\n }\n \n /* COREDUMP_RECORDS applies to a coredump the kernel writes, nothing else. */\n@@ -2320,4 +1942,674 @@ TEST_F(coredump, socket_request_stream_choice_large)\n \tASSERT_LT(choice.received, choice.size / 8);\n }\n \n+/* What a coredump server was built with. */\n+struct server_build {\n+\t/* sizeof(struct coredump_req) and sizeof(struct coredump_ack) back then. */\n+\tsize_t req_size;\n+\tsize_t ack_size;\n+\t/* The features it raises if the kernel offers them. */\n+\t__u64 wants;\n+\t/* Its policy: what it drops from and adds to the task's selection. */\n+\t__u64 drop;\n+\t__u64 add;\n+};\n+\n+/* A server from when the structs were first published: kernel-written dumps. */\n+static const struct server_build server_build_ver0 = {\n+\t.req_size\t= COREDUMP_REQ_SIZE_VER0,\n+\t.ack_size\t= COREDUMP_ACK_SIZE_VER0,\n+\t.wants\t\t= COREDUMP_KERNEL,\n+};\n+\n+/* A server built against this header: no shared memory, always the ELF headers. */\n+static const struct server_build server_build_ver1 = {\n+\t.req_size\t= sizeof(struct coredump_req),\n+\t.ack_size\t= sizeof(struct coredump_ack),\n+\t.wants\t\t= COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |\n+\t\t\t COREDUMP_MEMORY_TYPES,\n+\t.drop\t\t= COREDUMP_MEMORY_ANON_SHARED | COREDUMP_MEMORY_FILE_SHARED,\n+\t.add\t\t= COREDUMP_MEMORY_ELF_HEADERS,\n+};\n+\n+/*\n+ * Build the ack the way a server does: from what the kernel offers, what\n+ * this build implements, and what fits in the ack the kernel accepts.\n+ * Fields the build never read are zero and never consulted.\n+ */\n+static void negotiate(const struct coredump_req *req,\n+\t\t const struct server_build *build,\n+\t\t struct coredump_ack *ack)\n+{\n+\t__u64 offered = req-\u003emask \u0026 build-\u003ewants;\n+\n+\tmemset(ack, 0, sizeof(*ack));\n+\tack-\u003esize = build-\u003eack_size \u003c req-\u003esize_ack ? build-\u003eack_size : req-\u003esize_ack;\n+\t/* These builds only ever have the kernel write the coredump. */\n+\tack-\u003emask = COREDUMP_KERNEL;\n+\n+\t/* Sparse needs records, records need the kernel to write. */\n+\tif (offered \u0026 COREDUMP_RECORDS) {\n+\t\tack-\u003emask |= COREDUMP_RECORDS;\n+\t\tif (offered \u0026 COREDUMP_SPARSE)\n+\t\t\tack-\u003emask |= COREDUMP_SPARSE;\n+\t}\n+\n+\t/* The memory types need an ack that carries them. */\n+\tif ((offered \u0026 COREDUMP_MEMORY_TYPES) \u0026\u0026 ack-\u003esize \u003e= COREDUMP_ACK_SIZE_VER1) {\n+\t\tack-\u003emask |= COREDUMP_MEMORY_TYPES;\n+\t\t/* Start from the task's selection; only advertised types pass. */\n+\t\tack-\u003ememory_types = (req-\u003ememory_types \u0026 ~build-\u003edrop) | build-\u003eadd;\n+\t\tack-\u003ememory_types \u0026= req-\u003ememory_types_mask;\n+\t}\n+}\n+\n+/* What a memory types test asks of the kernel and what it expects back. */\n+struct memory_choice {\n+\t/* Memory types the crashing child selects, or FILTER_TASK_INHERIT. */\n+\t__u64 task_filter;\n+\t/* Negotiate the ack as this server build, NULL to send it as given. */\n+\tconst struct server_build *build;\n+\t/* The ack, or what the negotiation must arrive at. */\n+\t__u64 mask;\n+\t__u64 memory_types;\n+\tsize_t size_ack;\n+\t/* The shared mapping is in the coredump with all of its memory. */\n+\tbool shared_dumped;\n+\t/* No memory at all. Pull a page from /proc/\u003cpid\u003e/mem instead. */\n+\tbool skeleton;\n+};\n+\n+/* A skeleton still carries the vdso and friends, nothing bigger. */\n+#define SKELETON_DATA_PAGES 16\n+\n+/*\n+ * The crashing child maps shared anonymous memory and tells the server\n+ * where. The server acks with @choice and checks whether that mapping's\n+ * segment in the coredump carries its memory.\n+ */\n+static void check_memory_dump(struct __test_metadata *const _metadata,\n+\t\t\t FIXTURE_DATA(coredump) *self,\n+\t\t\t const struct memory_choice *choice)\n+{\n+\tint pidfd, status;\n+\tpid_t pid, pid_coredump_server;\n+\tstruct pidfd_info info = {};\n+\tint ipc_sockets[2];\n+\tint addr_pipe[2];\n+\tchar c;\n+\n+\tASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);\n+\tASSERT_EQ(pipe(addr_pipe), 0);\n+\tASSERT_TRUE(set_core_pattern(\"@@/tmp/coredump.socket\"));\n+\n+\tpid_coredump_server = fork();\n+\tASSERT_GE(pid_coredump_server, 0);\n+\tif (pid_coredump_server == 0) {\n+\t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;\n+\t\tint fd_file = -1;\n+\t\tint exit_code = EXIT_FAILURE;\n+\t\tstruct coredump_req req = {};\n+\t\tstruct coredump_ack ack = {\n+\t\t\t.size = choice-\u003esize_ack,\n+\t\t\t.mask = choice-\u003emask,\n+\t\t\t.memory_types = choice-\u003ememory_types,\n+\t\t};\n+\t\t/* How much of the request this server reads. */\n+\t\tsize_t req_size = choice-\u003ebuild ? choice-\u003ebuild-\u003ereq_size : sizeof(req);\n+\t\t__u64 task_filter;\n+\t\tElfW(Phdr) segment;\n+\t\tssize_t received;\n+\t\toff_t size;\n+\t\tchar *addr;\n+\n+\t\tclose(ipc_sockets[0]);\n+\t\tclose(addr_pipe[1]);\n+\n+\t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n+\t\tif (fd_server \u003c 0)\n+\t\t\tgoto out;\n+\n+\t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0)\n+\t\t\tgoto out;\n+\n+\t\tclose(ipc_sockets[1]);\n+\n+\t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n+\t\tif (fd_coredump \u003c 0)\n+\t\t\tgoto out;\n+\n+\t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n+\t\tif (fd_peer_pidfd \u003c 0)\n+\t\t\tgoto out;\n+\n+\t\tfd_file = open_coredump_tmpfile(self-\u003efd_tmpfs_detached);\n+\t\tif (fd_file \u003c 0)\n+\t\t\tgoto out;\n+\n+\t\tif (!read_coredump_req_sized(fd_coredump, \u0026req, req_size))\n+\t\t\tgoto out;\n+\n+\t\tif (!peer_coredump_filter(fd_peer_pidfd, \u0026task_filter))\n+\t\t\tgoto out;\n+\n+\t\t/* A build from before the memory types never read that far. */\n+\t\tif (req_size \u003e= COREDUMP_REQ_SIZE_VER1) {\n+\t\t\tif (!check_coredump_req(\u0026req))\n+\t\t\t\tgoto out;\n+\n+\t\t\t/* The request reports the memory types the task selected. */\n+\t\t\tif (req.memory_types != task_filter) {\n+\t\t\t\tfprintf(stderr, \"Request reports 0x%llx, task selected 0x%llx\\n\",\n+\t\t\t\t\t(unsigned long long)req.memory_types,\n+\t\t\t\t\t(unsigned long long)task_filter);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (choice-\u003etask_filter != FILTER_TASK_INHERIT \u0026\u0026\n+\t\t task_filter != choice-\u003etask_filter) {\n+\t\t\tfprintf(stderr, \"Task selected 0x%llx, child asked for 0x%llx\\n\",\n+\t\t\t\t(unsigned long long)task_filter,\n+\t\t\t\t(unsigned long long)choice-\u003etask_filter);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/* The child sent the address of its mapping before it crashed. */\n+\t\tif (read_nointr(addr_pipe[0], \u0026addr, sizeof(addr)) != sizeof(addr))\n+\t\t\tgoto out;\n+\n+\t\t/* A server build negotiates its ack and must arrive at the choice. */\n+\t\tif (choice-\u003ebuild) {\n+\t\t\tnegotiate(\u0026req, choice-\u003ebuild, \u0026ack);\n+\n+\t\t\tif (ack.size != choice-\u003esize_ack || ack.mask != choice-\u003emask ||\n+\t\t\t ack.memory_types != choice-\u003ememory_types) {\n+\t\t\t\tfprintf(stderr,\n+\t\t\t\t\t\"Negotiated %u bytes, mask 0x%llx, types 0x%llx\\n\",\n+\t\t\t\t\tack.size, (unsigned long long)ack.mask,\n+\t\t\t\t\t(unsigned long long)ack.memory_types);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!send_coredump_ack_types(fd_coredump, \u0026req, ack.mask,\n+\t\t\t\t\t ack.memory_types, ack.size))\n+\t\t\tgoto out;\n+\n+\t\tif (!read_marker(fd_coredump, COREDUMP_MARK_REQACK))\n+\t\t\tgoto out;\n+\n+\t\tif (ack.mask \u0026 COREDUMP_RECORDS)\n+\t\t\treceived = recv_coredump_records(fd_coredump, fd_file,\n+\t\t\t\t\t\t\t \u0026size, NULL, -1);\n+\t\telse\n+\t\t\treceived = recv_coredump_bytes(fd_coredump, fd_file);\n+\t\tif (received \u003c 0)\n+\t\t\tgoto out;\n+\n+\t\tif (!is_elf_core(fd_file))\n+\t\t\tgoto out;\n+\n+\t\t/* A dump ending in holes or empty segments must still be whole. */\n+\t\tif (!check_coredump_extent(fd_file))\n+\t\t\tgoto out;\n+\n+\t\tif (!find_coredump_segment(fd_file, (__u64)(uintptr_t)addr, \u0026segment))\n+\t\t\tgoto out;\n+\n+\t\tif (segment.p_memsz != MEMORY_MAPPING_SIZE) {\n+\t\t\tfprintf(stderr, \"Segment spans %llu bytes, the mapping %u\\n\",\n+\t\t\t\t(unsigned long long)segment.p_memsz,\n+\t\t\t\tMEMORY_MAPPING_SIZE);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (segment.p_filesz != (choice-\u003eshared_dumped ? segment.p_memsz : 0)) {\n+\t\t\tfprintf(stderr, \"Segment carries %llu bytes, expected %s of them\\n\",\n+\t\t\t\t(unsigned long long)segment.p_filesz,\n+\t\t\t\tchoice-\u003eshared_dumped ? \"all\" : \"none\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (choice-\u003eskeleton) {\n+\t\t\t__u64 data, notes, data_max;\n+\t\t\tchar buf[PAGE_SIZE];\n+\n+\t\t\tif (!sum_coredump_segments(fd_file, \u0026data, \u0026notes))\n+\t\t\t\tgoto out;\n+\n+\t\t\tdata_max = SKELETON_DATA_PAGES * sysconf(_SC_PAGESIZE);\n+\t\t\tif (!notes || data \u003e data_max) {\n+\t\t\t\tfprintf(stderr, \"Skeleton has %llu note and %llu memory bytes\\n\",\n+\t\t\t\t\t(unsigned long long)notes,\n+\t\t\t\t\t(unsigned long long)data);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\t/* The task is parked in COREDUMP_WAIT with its memory. */\n+\t\t\tif (peer_read_mem(fd_peer_pidfd, (__u64)(uintptr_t)addr,\n+\t\t\t\t\t buf, sizeof(buf)) != sizeof(buf))\n+\t\t\t\tgoto out;\n+\n+\t\t\tif (buf[0] != 'x') {\n+\t\t\t\tfprintf(stderr, \"Pulled memory lacks the child's mark\\n\");\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tfprintf(stderr, \"Skeleton of %zd bytes, pulled %zu bytes of memory\\n\",\n+\t\t\t\treceived, sizeof(buf));\n+\t\t}\n+\n+\t\texit_code = EXIT_SUCCESS;\n+out:\n+\t\tclose(addr_pipe[0]);\n+\t\tif (fd_file \u003e= 0)\n+\t\t\tclose(fd_file);\n+\t\tif (fd_peer_pidfd \u003e= 0)\n+\t\t\tclose(fd_peer_pidfd);\n+\t\tif (fd_coredump \u003e= 0)\n+\t\t\tclose(fd_coredump);\n+\t\tif (fd_server \u003e= 0)\n+\t\t\tclose(fd_server);\n+\t\t_exit(exit_code);\n+\t}\n+\tself-\u003epid_coredump_server = pid_coredump_server;\n+\n+\tEXPECT_EQ(close(ipc_sockets[1]), 0);\n+\tEXPECT_EQ(close(addr_pipe[0]), 0);\n+\tASSERT_EQ(read_nointr(ipc_sockets[0], \u0026c, 1), 1);\n+\tEXPECT_EQ(close(ipc_sockets[0]), 0);\n+\n+\tpid = fork();\n+\tASSERT_GE(pid, 0);\n+\tif (pid == 0)\n+\t\tcrashing_child_memory(choice-\u003etask_filter, addr_pipe[1]);\n+\tEXPECT_EQ(close(addr_pipe[1]), 0);\n+\n+\tpidfd = sys_pidfd_open(pid, 0);\n+\tASSERT_GE(pidfd, 0);\n+\n+\twaitpid(pid, \u0026status, 0);\n+\tASSERT_TRUE(WIFSIGNALED(status));\n+\tASSERT_TRUE(WCOREDUMP(status));\n+\n+\tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n+\tASSERT_GT((info.mask \u0026 PIDFD_INFO_COREDUMP), 0);\n+\tASSERT_GT((info.coredump_mask \u0026 PIDFD_COREDUMPED), 0);\n+\n+\twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n+}\n+\n+/* Without COREDUMP_MEMORY_TYPES the task's own selection decides. */\n+TEST_F(coredump, socket_request_memory_types_task_includes)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t COREDUMP_MEMORY_ANON_SHARED,\n+\t\t.mask = COREDUMP_KERNEL,\n+\t\t.shared_dumped = true,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+TEST_F(coredump, socket_request_memory_types_task_excludes)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = 0,\n+\t\t.mask = COREDUMP_KERNEL,\n+\t\t.shared_dumped = false,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+/* The server drops a memory type the task would have dumped. */\n+TEST_F(coredump, socket_request_memory_types_restricts)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t COREDUMP_MEMORY_ANON_SHARED,\n+\t\t.mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,\n+\t\t.memory_types = COREDUMP_MEMORY_ANON_PRIVATE,\n+\t\t.shared_dumped = false,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+/* The server adds a memory type the task had excluded. */\n+TEST_F(coredump, socket_request_memory_types_widens)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = 0,\n+\t\t.mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,\n+\t\t.memory_types = COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t COREDUMP_MEMORY_ANON_SHARED,\n+\t\t.shared_dumped = true,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+/* The memory types decide what goes into a record stream just the same. */\n+TEST_F(coredump, socket_request_memory_types_records)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = FILTER_TASK_INHERIT,\n+\t\t.mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |\n+\t\t\tCOREDUMP_MEMORY_TYPES,\n+\t\t.memory_types = COREDUMP_MEMORY_ANON_PRIVATE,\n+\t\t.shared_dumped = false,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+/*\n+ * An empty selection leaves a skeleton: every program header and every note\n+ * but no memory. A server that wants to pick the memory itself reads it\n+ * from /proc/\u003cpid\u003e/mem while the task waits for it to finish.\n+ */\n+TEST_F(coredump, socket_request_memory_types_skeleton)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = FILTER_TASK_INHERIT,\n+\t\t.mask = COREDUMP_KERNEL | COREDUMP_WAIT | COREDUMP_MEMORY_TYPES,\n+\t\t.memory_types = 0,\n+\t\t.shared_dumped = false,\n+\t\t.skeleton = true,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+/* A memory type the kernel didn't advertise in memory_types_mask. */\n+TEST_F(coredump, socket_request_memory_types_unknown_bit)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = sizeof(struct coredump_ack),\n+\t\t\t.mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,\n+\t\t\t.memory_types = 1ULL \u003c\u003c 63,\n+\t\t},\n+\t\t.bytes = sizeof(struct coredump_ack),\n+\t\t.mark = COREDUMP_MARK_UNSUPPORTED,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* The memory types must be zero unless COREDUMP_MEMORY_TYPES is raised. */\n+TEST_F(coredump, socket_request_memory_types_stale_field)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = sizeof(struct coredump_ack),\n+\t\t\t.mask = COREDUMP_KERNEL,\n+\t\t\t.memory_types = COREDUMP_MEMORY_ANON_PRIVATE,\n+\t\t},\n+\t\t.bytes = sizeof(struct coredump_ack),\n+\t\t.mark = COREDUMP_MARK_UNSUPPORTED,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* COREDUMP_MEMORY_TYPES needs an ack that has the memory types. */\n+TEST_F(coredump, socket_request_memory_types_short_ack)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = COREDUMP_ACK_SIZE_VER0,\n+\t\t\t.mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,\n+\t\t},\n+\t\t.bytes = COREDUMP_ACK_SIZE_VER0,\n+\t\t.mark = COREDUMP_MARK_MINSIZE,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* The memory types select what the kernel writes, nothing else. */\n+TEST_F(coredump, socket_request_memory_types_without_kernel)\n+{\n+\tcheck_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_MEMORY_TYPES);\n+}\n+\n+/*\n+ * A server built with the first structs reads the request it knows,\n+ * discards the rest and acks with the ack it knows. It raises nothing\n+ * it wasn't built for and the kernel dumps what the task selected.\n+ */\n+TEST_F(coredump, socket_request_negotiate_ver0)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t COREDUMP_MEMORY_ANON_SHARED,\n+\t\t.build = \u0026server_build_ver0,\n+\t\t.mask = COREDUMP_KERNEL,\n+\t\t.memory_types = 0,\n+\t\t.size_ack = COREDUMP_ACK_SIZE_VER0,\n+\t\t.shared_dumped = true,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+/*\n+ * A server built against this header takes every feature the kernel\n+ * offers, drops shared memory from what the task selected and adds the\n+ * ELF headers.\n+ */\n+TEST_F(coredump, socket_request_negotiate_ver1)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t COREDUMP_MEMORY_ANON_SHARED,\n+\t\t.build = \u0026server_build_ver1,\n+\t\t.mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |\n+\t\t\tCOREDUMP_MEMORY_TYPES,\n+\t\t.memory_types = COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t\t COREDUMP_MEMORY_ELF_HEADERS,\n+\t\t.size_ack = COREDUMP_ACK_SIZE_VER1,\n+\t\t.shared_dumped = false,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+/* An ack that picks none of KERNEL, USERSPACE and REJECT. */\n+TEST_F(coredump, socket_request_no_mode)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = sizeof(struct coredump_ack),\n+\t\t\t.mask = COREDUMP_WAIT,\n+\t\t},\n+\t\t.bytes = sizeof(struct coredump_ack),\n+\t\t.mark = COREDUMP_MARK_CONFLICTING,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* @spare must be zero, like every field that isn't in use. */\n+TEST_F(coredump, socket_request_spare)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = sizeof(struct coredump_ack),\n+\t\t\t.spare = 1,\n+\t\t\t.mask = COREDUMP_KERNEL,\n+\t\t},\n+\t\t.bytes = sizeof(struct coredump_ack),\n+\t\t.mark = COREDUMP_MARK_UNSUPPORTED,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* An ack size is a byte count. One that ends inside a field is valid. */\n+#define ACK_SIZE_BETWEEN (COREDUMP_ACK_SIZE_VER0 + sizeof(__u32))\n+\n+/* Any size from VER0 up to what the kernel accepts works without memory types. */\n+TEST_F(coredump, socket_request_ack_size_between)\n+{\n+\tstruct memory_choice choice = {\n+\t\t.task_filter = COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t COREDUMP_MEMORY_ANON_SHARED,\n+\t\t.mask = COREDUMP_KERNEL,\n+\t\t.size_ack = ACK_SIZE_BETWEEN,\n+\t\t.shared_dumped = true,\n+\t};\n+\n+\tcheck_memory_dump(_metadata, self, \u0026choice);\n+}\n+\n+/* The memory types need the whole field, not the part that happens to fit. */\n+TEST_F(coredump, socket_request_memory_types_ack_size_between)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = ACK_SIZE_BETWEEN,\n+\t\t\t.mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,\n+\t\t},\n+\t\t.bytes = ACK_SIZE_BETWEEN,\n+\t\t.mark = COREDUMP_MARK_MINSIZE,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* A server that hangs up without acking gets no marker and no coredump. */\n+TEST_F(coredump, socket_request_server_hangs_up)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.bytes = 0,\n+\t\t.no_marker = true,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/* A server that hangs up in the middle of its ack looks the same. */\n+TEST_F(coredump, socket_request_ack_truncated)\n+{\n+\tstruct refused_ack refused = {\n+\t\t.ack = {\n+\t\t\t.size = COREDUMP_ACK_SIZE_VER0,\n+\t\t\t.mask = COREDUMP_KERNEL,\n+\t\t},\n+\t\t.bytes = COREDUMP_ACK_SIZE_VER0 / 2,\n+\t\t.no_marker = true,\n+\t};\n+\n+\tcheck_refused_ack(_metadata, self, \u0026refused);\n+}\n+\n+/*\n+ * The kernels a server built against this header can't meet here:\n+ * negotiate() against their requests, no coredump involved.\n+ */\n+\n+/* The request of a kernel with the first structs and features. */\n+static const struct coredump_req req_ver0 = {\n+\t.size\t\t\t= COREDUMP_REQ_SIZE_VER0,\n+\t.size_ack\t\t= COREDUMP_ACK_SIZE_VER0,\n+\t.mask\t\t\t= COREDUMP_KERNEL | COREDUMP_USERSPACE |\n+\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT,\n+};\n+\n+/* The request of this kernel. */\n+static const struct coredump_req req_ver1 = {\n+\t.size\t\t\t= COREDUMP_REQ_SIZE_VER1,\n+\t.size_ack\t\t= COREDUMP_ACK_SIZE_VER1,\n+\t.mask\t\t\t= COREDUMP_KERNEL | COREDUMP_USERSPACE |\n+\t\t\t\t COREDUMP_REJECT | COREDUMP_WAIT |\n+\t\t\t\t COREDUMP_RECORDS | COREDUMP_SPARSE |\n+\t\t\t\t COREDUMP_MEMORY_TYPES,\n+\t.memory_types\t\t= COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t\t COREDUMP_MEMORY_ANON_SHARED,\n+\t.memory_types_mask\t= TEST_MEMORY_ALL,\n+};\n+\n+/* A kernel with the first structs gets the first ack and nothing newer. */\n+TEST(negotiate_ver0_kernel)\n+{\n+\tstruct coredump_ack ack;\n+\n+\tnegotiate(\u0026req_ver0, \u0026server_build_ver1, \u0026ack);\n+\tASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);\n+\tASSERT_EQ(ack.mask, COREDUMP_KERNEL);\n+\tASSERT_EQ(ack.memory_types, 0);\n+}\n+\n+/* A kernel with records and sparse but the first structs: both, no types. */\n+TEST(negotiate_sparse_kernel)\n+{\n+\tstruct coredump_req req = req_ver0;\n+\tstruct coredump_ack ack;\n+\n+\treq.mask |= COREDUMP_RECORDS | COREDUMP_SPARSE;\n+\tnegotiate(\u0026req, \u0026server_build_ver1, \u0026ack);\n+\tASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);\n+\tASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE);\n+\tASSERT_EQ(ack.memory_types, 0);\n+}\n+\n+/* Records without sparse: sparse isn't raised on its own. */\n+TEST(negotiate_records_without_sparse)\n+{\n+\tstruct coredump_req req = req_ver0;\n+\tstruct coredump_ack ack;\n+\n+\treq.mask |= COREDUMP_RECORDS;\n+\tnegotiate(\u0026req, \u0026server_build_ver1, \u0026ack);\n+\tASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS);\n+}\n+\n+/*\n+ * A feature whose ack field lies past what the kernel accepts can't be\n+ * raised. No kernel offers the memory types without the room for them, so a\n+ * request that does stands in for a feature newer than this header.\n+ */\n+TEST(negotiate_types_need_room)\n+{\n+\tstruct coredump_req req = req_ver0;\n+\tstruct coredump_ack ack;\n+\n+\treq.mask |= COREDUMP_MEMORY_TYPES;\n+\tnegotiate(\u0026req, \u0026server_build_ver1, \u0026ack);\n+\tASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);\n+\tASSERT_EQ(ack.mask, COREDUMP_KERNEL);\n+\tASSERT_EQ(ack.memory_types, 0);\n+}\n+\n+/* This kernel: the policy applied to the task's selection. */\n+TEST(negotiate_ver1_kernel)\n+{\n+\tstruct coredump_ack ack;\n+\n+\tnegotiate(\u0026req_ver1, \u0026server_build_ver1, \u0026ack);\n+\tASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER1);\n+\tASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |\n+\t\t\t COREDUMP_MEMORY_TYPES);\n+\tASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE |\n+\t\t\t\t COREDUMP_MEMORY_ELF_HEADERS);\n+}\n+\n+/* A kernel that doesn't know a type the policy adds isn't asked for it. */\n+TEST(negotiate_unknown_type)\n+{\n+\tstruct coredump_req req = req_ver1;\n+\tstruct coredump_ack ack;\n+\n+\treq.memory_types_mask \u0026= ~(__u64)COREDUMP_MEMORY_ELF_HEADERS;\n+\tnegotiate(\u0026req, \u0026server_build_ver1, \u0026ack);\n+\tASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |\n+\t\t\t COREDUMP_MEMORY_TYPES);\n+\tASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE);\n+}\n+\n TEST_HARNESS_MAIN\ndiff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c\nindex d7cc448eeaf44..4e36e3e4fb78f 100644\n--- a/tools/testing/selftests/coredump/coredump_test_helpers.c\n+++ b/tools/testing/selftests/coredump/coredump_test_helpers.c\n@@ -17,6 +17,7 @@\n #include \u003csys/ioctl.h\u003e\n #include \u003csys/mman.h\u003e\n #include \u003csys/socket.h\u003e\n+#include \u003csys/stat.h\u003e\n #include \u003csys/types.h\u003e\n #include \u003csys/un.h\u003e\n #include \u003csys/wait.h\u003e\n@@ -73,6 +74,48 @@ void crashing_child_sparse(size_t size)\n \t*(volatile int *)NULL = 0;\n }\n \n+/* Select @types through the caller's own /proc/self/coredump_filter. */\n+static bool set_coredump_filter(__u64 types)\n+{\n+\tchar buf[32];\n+\tint fd, len;\n+\tbool ok;\n+\n+\tfd = open(\"/proc/self/coredump_filter\", O_WRONLY | O_CLOEXEC);\n+\tif (fd \u003c 0)\n+\t\treturn false;\n+\n+\tlen = snprintf(buf, sizeof(buf), \"0x%llx\", (unsigned long long)types);\n+\tok = write_nointr(fd, buf, len) == len;\n+\tclose(fd);\n+\treturn ok;\n+}\n+\n+/*\n+ * Map shared anonymous memory, touch it, tell the server where it is and\n+ * crash. A @task_filter other than FILTER_TASK_INHERIT is selected first.\n+ */\n+void crashing_child_memory(__u64 task_filter, int fd_addr)\n+{\n+\tchar *p;\n+\n+\tif (task_filter != FILTER_TASK_INHERIT \u0026\u0026 !set_coredump_filter(task_filter))\n+\t\t_exit(EXIT_FAILURE);\n+\n+\tp = mmap(NULL, MEMORY_MAPPING_SIZE, PROT_READ | PROT_WRITE,\n+\t\t MAP_SHARED | MAP_ANONYMOUS, -1, 0);\n+\tif (p == MAP_FAILED)\n+\t\t_exit(EXIT_FAILURE);\n+\tp[0] = 'x';\n+\n+\tif (write_nointr(fd_addr, \u0026p, sizeof(p)) != sizeof(p))\n+\t\t_exit(EXIT_FAILURE);\n+\tclose(fd_addr);\n+\n+\t/* crash on purpose */\n+\t*(volatile int *)NULL = 0;\n+}\n+\n /* Sink a reassembled record stream is handed to, record by record. */\n struct coredump_record_sink {\n \t/* @len bytes of coredump data that belong at @offset. */\n@@ -916,6 +959,82 @@ static const ElfW(Phdr) *find_segment(const ElfW(Phdr) *phdr, size_t nr,\n \treturn NULL;\n }\n \n+/* The PT_LOAD segment @vaddr falls into. */\n+bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment)\n+{\n+\tconst ElfW(Phdr) *found;\n+\tElfW(Phdr) *phdr;\n+\tsize_t nr;\n+\n+\tphdr = read_phdrs(fd, \u0026nr);\n+\tif (!phdr)\n+\t\treturn false;\n+\n+\tfound = find_segment(phdr, nr, vaddr);\n+\tif (found)\n+\t\t*segment = *found;\n+\telse\n+\t\tfprintf(stderr, \"%s: no segment for 0x%llx\\n\", __func__,\n+\t\t\t(unsigned long long)vaddr);\n+\n+\tfree(phdr);\n+\treturn found;\n+}\n+\n+/* How many bytes the PT_LOAD and the PT_NOTE segments of @fd carry. */\n+bool sum_coredump_segments(int fd, __u64 *data, __u64 *notes)\n+{\n+\tElfW(Phdr) *phdr;\n+\tsize_t nr, i;\n+\n+\tphdr = read_phdrs(fd, \u0026nr);\n+\tif (!phdr)\n+\t\treturn false;\n+\n+\t*data = 0;\n+\t*notes = 0;\n+\tfor (i = 0; i \u003c nr; i++) {\n+\t\tif (phdr[i].p_type == PT_LOAD)\n+\t\t\t*data += phdr[i].p_filesz;\n+\t\telse if (phdr[i].p_type == PT_NOTE)\n+\t\t\t*notes += phdr[i].p_filesz;\n+\t}\n+\n+\tfree(phdr);\n+\treturn true;\n+}\n+\n+/* The coredump in @fd is at least as long as every segment it declares. */\n+bool check_coredump_extent(int fd)\n+{\n+\tElfW(Phdr) *phdr;\n+\tstruct stat st;\n+\tsize_t nr, i;\n+\tbool ok = true;\n+\n+\tif (fstat(fd, \u0026st)) {\n+\t\tfprintf(stderr, \"%s: fstat: %m\\n\", __func__);\n+\t\treturn false;\n+\t}\n+\n+\tphdr = read_phdrs(fd, \u0026nr);\n+\tif (!phdr)\n+\t\treturn false;\n+\n+\tfor (i = 0; i \u003c nr; i++) {\n+\t\tif (phdr[i].p_offset + phdr[i].p_filesz \u003c= (__u64)st.st_size)\n+\t\t\tcontinue;\n+\t\tfprintf(stderr, \"%s: segment %zu ends at %llu, the coredump at %llu\\n\",\n+\t\t\t__func__, i,\n+\t\t\t(unsigned long long)(phdr[i].p_offset + phdr[i].p_filesz),\n+\t\t\t(unsigned long long)st.st_size);\n+\t\tok = false;\n+\t}\n+\n+\tfree(phdr);\n+\treturn ok;\n+}\n+\n /* The next stretch of memory the segments cover, split ones merged back. */\n static bool next_range(const ElfW(Phdr) *phdr, size_t nr, size_t *i,\n \t\t __u64 *start, __u64 *end)\n@@ -1250,6 +1369,62 @@ ssize_t peer_vm_size(int fd_peer_pidfd)\n \n /* Protocol helper functions */\n \n+/* The peer's /proc/\u003cpid\u003e/coredump_filter, which is in memory types. */\n+bool peer_coredump_filter(int fd_peer_pidfd, __u64 *memory_types)\n+{\n+\tstruct pidfd_info info = {};\n+\tunsigned long value;\n+\tchar path[64];\n+\tFILE *f;\n+\tint ret;\n+\n+\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info))\n+\t\treturn false;\n+\n+\tsnprintf(path, sizeof(path), \"/proc/%d/coredump_filter\", info.pid);\n+\tf = fopen(path, \"r\");\n+\tif (!f) {\n+\t\tfprintf(stderr, \"%s: %s: %m\\n\", __func__, path);\n+\t\treturn false;\n+\t}\n+\n+\tret = fscanf(f, \"%lx\", \u0026value);\n+\tfclose(f);\n+\tif (ret != 1) {\n+\t\tfprintf(stderr, \"%s: %s: no value\\n\", __func__, path);\n+\t\treturn false;\n+\t}\n+\n+\t*memory_types = value;\n+\treturn true;\n+}\n+\n+/* Read @len bytes at @addr from the peer's /proc/\u003cpid\u003e/mem. */\n+ssize_t peer_read_mem(int fd_peer_pidfd, __u64 addr, void *buf, size_t len)\n+{\n+\tstruct pidfd_info info = {};\n+\tchar path[64];\n+\tssize_t ret;\n+\tint fd;\n+\n+\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info))\n+\t\treturn -1;\n+\n+\tsnprintf(path, sizeof(path), \"/proc/%d/mem\", info.pid);\n+\tfd = open(path, O_RDONLY | O_CLOEXEC);\n+\tif (fd \u003c 0) {\n+\t\tfprintf(stderr, \"%s: %s: %m\\n\", __func__, path);\n+\t\treturn -1;\n+\t}\n+\n+\tret = pread(fd, buf, len, addr);\n+\tif (ret \u003c 0)\n+\t\tfprintf(stderr, \"%s: %s at 0x%llx: %m\\n\", __func__, path,\n+\t\t\t(unsigned long long)addr);\n+\tclose(fd);\n+\treturn ret;\n+}\n+\n ssize_t recv_marker(int fd)\n {\n \tenum coredump_mark mark = COREDUMP_MARK_REQACK;\n@@ -1292,10 +1467,34 @@ bool read_marker(int fd, enum coredump_mark mark)\n \treturn ret == mark;\n }\n \n-bool read_coredump_req(int fd, struct coredump_req *req)\n+/*\n+ * The kernel hung up without sending anything more: end of stream, or a\n+ * reset if it refused the ack on its peeked size and never read it.\n+ */\n+bool read_hangup(int fd)\n {\n \tssize_t ret;\n-\tsize_t field_size, user_size, known_size, kernel_size, remaining_size;\n+\tchar c;\n+\n+\tret = recv(fd, \u0026c, sizeof(c), MSG_WAITALL);\n+\tif (ret == 0) {\n+\t\tfprintf(stderr, \"Kernel closed the connection\\n\");\n+\t\treturn true;\n+\t}\n+\tif (ret \u003c 0 \u0026\u0026 errno == ECONNRESET) {\n+\t\tfprintf(stderr, \"Kernel closed the connection with the ack unread\\n\");\n+\t\treturn true;\n+\t}\n+\n+\tfprintf(stderr, \"%s: expected a hangup, got %zd: %m\\n\", __func__, ret);\n+\treturn false;\n+}\n+\n+/* Read the request as a server built with a @user_size byte struct does. */\n+bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size)\n+{\n+\tssize_t ret;\n+\tsize_t field_size, known_size, kernel_size, remaining_size;\n \n \tmemset(req, 0, sizeof(*req));\n \tfield_size = sizeof(req-\u003esize);\n@@ -1303,25 +1502,24 @@ bool read_coredump_req(int fd, struct coredump_req *req)\n \t/* Peek the size of the coredump request. */\n \tret = recv(fd, req, field_size, MSG_PEEK | MSG_WAITALL);\n \tif (ret != field_size) {\n-\t\tfprintf(stderr, \"read_coredump_req: peek failed (got %zd, expected %zu): %m\\n\",\n+\t\tfprintf(stderr, \"%s: peek failed (got %zd, expected %zu): %m\\n\", __func__,\n \t\t\tret, field_size);\n \t\treturn false;\n \t}\n \tkernel_size = req-\u003esize;\n \n \tif (kernel_size \u003c COREDUMP_REQ_SIZE_VER0) {\n-\t\tfprintf(stderr, \"read_coredump_req: kernel_size %zu \u003c min %d\\n\",\n+\t\tfprintf(stderr, \"%s: kernel_size %zu \u003c min %d\\n\", __func__,\n \t\t\tkernel_size, COREDUMP_REQ_SIZE_VER0);\n \t\treturn false;\n \t}\n \tif (kernel_size \u003e= PAGE_SIZE) {\n-\t\tfprintf(stderr, \"read_coredump_req: kernel_size %zu \u003e= PAGE_SIZE %d\\n\",\n+\t\tfprintf(stderr, \"%s: kernel_size %zu \u003e= PAGE_SIZE %d\\n\", __func__,\n \t\t\tkernel_size, PAGE_SIZE);\n \t\treturn false;\n \t}\n \n \t/* Consume as much of the request as we know about. */\n-\tuser_size = sizeof(struct coredump_req);\n \tknown_size = user_size \u003c kernel_size ? user_size : kernel_size;\n \tret = recv(fd, req, known_size, MSG_WAITALL);\n \tif (ret != known_size)\n@@ -1354,8 +1552,13 @@ bool read_coredump_req(int fd, struct coredump_req *req)\n \treturn true;\n }\n \n-bool send_coredump_ack(int fd, const struct coredump_req *req,\n-\t\t __u64 mask, size_t size_ack)\n+bool read_coredump_req(int fd, struct coredump_req *req)\n+{\n+\treturn read_coredump_req_sized(fd, req, sizeof(*req));\n+}\n+\n+/* Send @len bytes of @ack as they are, more than the struct if asked to. */\n+bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len)\n {\n \tssize_t ret;\n \t/*\n@@ -1367,34 +1570,60 @@ bool send_coredump_ack(int fd, const struct coredump_req *req,\n \t\tchar buffer[PAGE_SIZE];\n \t} large_ack = {};\n \n-\tif (!size_ack)\n-\t\tsize_ack = sizeof(struct coredump_ack) \u003c req-\u003esize_ack ?\n-\t\t\t\t sizeof(struct coredump_ack) :\n-\t\t\t\t req-\u003esize_ack;\n-\tlarge_ack.ack.mask = mask;\n-\tlarge_ack.ack.size = size_ack;\n-\tret = send(fd, \u0026large_ack, size_ack, MSG_NOSIGNAL);\n-\tif (ret != size_ack) {\n+\tif (len \u003e sizeof(large_ack))\n+\t\treturn false;\n+\n+\tlarge_ack.ack = *ack;\n+\tret = send(fd, \u0026large_ack, len, MSG_NOSIGNAL);\n+\tif (ret != len) {\n \t\tfprintf(stderr, \"%s: short send %zd: %m\\n\", __func__, ret);\n \t\treturn false;\n \t}\n \n-\tfprintf(stderr, \"Sent coredump ack with size %zu and mask 0x%llx\\n\",\n-\t\tsize_ack, (unsigned long long)mask);\n+\tfprintf(stderr, \"Sent %zu bytes of coredump ack: size %u, mask 0x%llx, types 0x%llx\\n\",\n+\t\tlen, ack-\u003esize, (unsigned long long)ack-\u003emask,\n+\t\t(unsigned long long)ack-\u003ememory_types);\n \treturn true;\n }\n \n+bool send_coredump_ack_types(int fd, const struct coredump_req *req,\n+\t\t\t __u64 mask, __u64 memory_types, size_t size_ack)\n+{\n+\tstruct coredump_ack ack = {\n+\t\t.mask = mask,\n+\t\t.memory_types = memory_types,\n+\t};\n+\n+\tif (!size_ack)\n+\t\tsize_ack = sizeof(struct coredump_ack) \u003c req-\u003esize_ack ?\n+\t\t\t\t sizeof(struct coredump_ack) :\n+\t\t\t\t req-\u003esize_ack;\n+\tack.size = size_ack;\n+\treturn send_coredump_ack_bytes(fd, \u0026ack, size_ack);\n+}\n+\n+bool send_coredump_ack(int fd, const struct coredump_req *req,\n+\t\t __u64 mask, size_t size_ack)\n+{\n+\treturn send_coredump_ack_types(fd, req, mask, 0, size_ack);\n+}\n+\n /* Every option the kernel is expected to advertise in coredump_req-\u003emask. */\n #define TEST_REQ_MASK_ALL\t\t\t\t\t\\\n \t(COREDUMP_KERNEL | COREDUMP_USERSPACE |\t\t\t\\\n \t COREDUMP_REJECT | COREDUMP_WAIT |\t\t\t\\\n-\t COREDUMP_RECORDS | COREDUMP_SPARSE)\n+\t COREDUMP_RECORDS | COREDUMP_SPARSE | COREDUMP_MEMORY_TYPES)\n \n bool check_coredump_req(const struct coredump_req *req)\n {\n-\tif (req-\u003esize \u003c COREDUMP_REQ_SIZE_VER0) {\n-\t\tfprintf(stderr, \"%s: size %u below minimum %d\\n\",\n-\t\t\t__func__, req-\u003esize, COREDUMP_REQ_SIZE_VER0);\n+\tif (req-\u003esize != COREDUMP_REQ_SIZE_VER1) {\n+\t\tfprintf(stderr, \"%s: size %u, expected %d\\n\",\n+\t\t\t__func__, req-\u003esize, COREDUMP_REQ_SIZE_VER1);\n+\t\treturn false;\n+\t}\n+\tif (req-\u003esize_ack != COREDUMP_ACK_SIZE_VER1) {\n+\t\tfprintf(stderr, \"%s: size_ack %u, expected %d\\n\",\n+\t\t\t__func__, req-\u003esize_ack, COREDUMP_ACK_SIZE_VER1);\n \t\treturn false;\n \t}\n \tif (req-\u003emask != TEST_REQ_MASK_ALL) {\n@@ -1403,6 +1632,12 @@ bool check_coredump_req(const struct coredump_req *req)\n \t\t\t(unsigned long long)TEST_REQ_MASK_ALL);\n \t\treturn false;\n \t}\n+\tif (req-\u003ememory_types_mask != TEST_MEMORY_ALL) {\n+\t\tfprintf(stderr, \"%s: memory_types_mask 0x%llx, expected 0x%llx\\n\",\n+\t\t\t__func__, (unsigned long long)req-\u003ememory_types_mask,\n+\t\t\t(unsigned long long)TEST_MEMORY_ALL);\n+\t\treturn false;\n+\t}\n \treturn true;\n }\n \ndiff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h\nindex 97ad5cfeae928..3f2f87837558e 100644\n--- a/tools/testing/selftests/coredump/coredump_test_helpers.h\n+++ b/tools/testing/selftests/coredump/coredump_test_helpers.h\n@@ -3,6 +3,7 @@\n #ifndef __COREDUMP_TEST_HELPERS_H\n #define __COREDUMP_TEST_HELPERS_H\n \n+#include \u003clink.h\u003e\n #include \u003cstdbool.h\u003e\n #include \u003csys/types.h\u003e\n #include \u003clinux/coredump.h\u003e\n@@ -21,10 +22,30 @@\n /* A task mapping at least this much is worth a record stream. */\n #define SPARSE_STREAM_THRESHOLD (SPARSE_MAPPING_SIZE / 2)\n \n+/* Size of the shared anonymous mapping the memory types tests map. */\n+#define MEMORY_MAPPING_SIZE (4 * 1024 * 1024)\n+\n+/* Leave the coredump_filter the crashing child inherited alone. */\n+#define FILTER_TASK_INHERIT ((__u64)-1)\n+\n+/* Every memory type the kernel is expected to advertise. */\n+#define TEST_MEMORY_ALL\t\t\t\t\t\t\\\n+\t(COREDUMP_MEMORY_ANON_PRIVATE | COREDUMP_MEMORY_ANON_SHARED |\t\\\n+\t COREDUMP_MEMORY_FILE_PRIVATE | COREDUMP_MEMORY_FILE_SHARED |\t\\\n+\t COREDUMP_MEMORY_ELF_HEADERS |\t\t\t\t\t\\\n+\t COREDUMP_MEMORY_HUGETLB_PRIVATE | COREDUMP_MEMORY_HUGETLB_SHARED | \\\n+\t COREDUMP_MEMORY_DAX_PRIVATE | COREDUMP_MEMORY_DAX_SHARED)\n+\n /* Shared helper function declarations */\n void *do_nothing(void *arg);\n void crashing_child(void);\n void crashing_child_sparse(size_t size);\n+void crashing_child_memory(__u64 task_filter, int fd_addr);\n+bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment);\n+bool sum_coredump_segments(int fd, __u64 *data, __u64 *notes);\n+bool check_coredump_extent(int fd);\n+bool peer_coredump_filter(int fd_peer_pidfd, __u64 *memory_types);\n+ssize_t peer_read_mem(int fd_peer_pidfd, __u64 addr, void *buf, size_t len);\n ssize_t recv_coredump_records(int fd_coredump, int fd_core_file,\n \t\t\t off_t *coredump_size, bool *truncated,\n \t\t\t int fd_peer_pidfd);\n@@ -43,9 +64,14 @@ bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);\n /* Protocol helper function declarations */\n ssize_t recv_marker(int fd);\n bool read_marker(int fd, enum coredump_mark mark);\n+bool read_hangup(int fd);\n bool read_coredump_req(int fd, struct coredump_req *req);\n+bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size);\n bool send_coredump_ack(int fd, const struct coredump_req *req,\n \t\t __u64 mask, size_t size_ack);\n+bool send_coredump_ack_types(int fd, const struct coredump_req *req,\n+\t\t\t __u64 mask, __u64 memory_types, size_t size_ack);\n+bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len);\n bool check_coredump_req(const struct coredump_req *req);\n int open_coredump_tmpfile(int fd_tmpfs_detached);\n void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file);\n"
} |
| 2/1 | 2026/08/21 13:49 | agent | patch-evaluator |
2mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"COREDUMP",
"UNIX",
"BINFMT_ELF",
"ELF_CORE",
"PROC_FS"
],
"FocusSymbols": [
"vfs_coredump"
],
"Reasoning": "The patch modifies the coredump socket protocol to allow the coredump server to dynamically select memory types to include in the core dump. This is a functional change in the core kernel's coredump logic, which is reachable when a process crashes and the core_pattern is set to use a socket. The changes are definitely worth fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 324a9577c7e70c4a55951ad6619bab74d40f523a
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Aug 21 13:49:49 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst
index 2ccd5b2dfdd33..3a309e8ee96bd 100644
--- a/Documentation/filesystems/proc.rst
+++ b/Documentation/filesystems/proc.rst
@@ -1962,6 +1962,10 @@ For example::
$ echo 0x7 > /proc/self/coredump_filter
$ ./some_program
+If the coredump socket protocol is used a coredump server can select memory
+types to include dynamically. See COREDUMP_MEMORY_TYPES in
+include/uapi/linux/coredump.h.
+
3.5 /proc/<pid>/mountinfo - Information about mounts
--------------------------------------------------------
diff --git a/fs/coredump.c b/fs/coredump.c
index 7b568d25887ce..ec9359e382ea5 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -759,10 +759,39 @@ static inline bool coredump_sock_send(struct file *file, struct coredump_req *re
return ret == sizeof(*req);
}
-static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER0);
-static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER0);
+static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER1);
+static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER1);
static_assert(sizeof(enum coredump_mark) == sizeof(__u32));
+/* Every memory type this kernel knows. */
+#define COREDUMP_MEMORY_ALL \
+ (COREDUMP_MEMORY_ANON_PRIVATE | COREDUMP_MEMORY_ANON_SHARED | \
+ COREDUMP_MEMORY_FILE_PRIVATE | COREDUMP_MEMORY_FILE_SHARED | \
+ COREDUMP_MEMORY_ELF_HEADERS | \
+ COREDUMP_MEMORY_HUGETLB_PRIVATE | COREDUMP_MEMORY_HUGETLB_SHARED | \
+ COREDUMP_MEMORY_DAX_PRIVATE | COREDUMP_MEMORY_DAX_SHARED)
+
+#define COREDUMP_MEMORY_TYPE_BIT(mmf) BIT((mmf) - MMF_DUMP_FILTER_SHIFT)
+static_assert(COREDUMP_MEMORY_ALL == (MMF_DUMP_FILTER_MASK >> MMF_DUMP_FILTER_SHIFT));
+static_assert(COREDUMP_MEMORY_ANON_PRIVATE ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ANON_PRIVATE));
+static_assert(COREDUMP_MEMORY_ANON_SHARED ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ANON_SHARED));
+static_assert(COREDUMP_MEMORY_FILE_PRIVATE ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_MAPPED_PRIVATE));
+static_assert(COREDUMP_MEMORY_FILE_SHARED ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_MAPPED_SHARED));
+static_assert(COREDUMP_MEMORY_ELF_HEADERS ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ELF_HEADERS));
+static_assert(COREDUMP_MEMORY_HUGETLB_PRIVATE ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_HUGETLB_PRIVATE));
+static_assert(COREDUMP_MEMORY_HUGETLB_SHARED ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_HUGETLB_SHARED));
+static_assert(COREDUMP_MEMORY_DAX_PRIVATE ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_DAX_PRIVATE));
+static_assert(COREDUMP_MEMORY_DAX_SHARED ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_DAX_SHARED));
+
static inline bool coredump_sock_mark(struct file *file, enum coredump_mark mark)
{
struct msghdr msg = { .msg_flags = MSG_NOSIGNAL };
@@ -804,11 +833,14 @@ static inline void coredump_sock_shutdown(struct file *file)
static bool coredump_sock_request(struct core_name *cn, struct coredump_params *cprm)
{
struct coredump_req req = {
- .size = sizeof(struct coredump_req),
- .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
- COREDUMP_REJECT | COREDUMP_WAIT |
- COREDUMP_RECORDS | COREDUMP_SPARSE,
- .size_ack = sizeof(struct coredump_ack),
+ .size = sizeof(struct coredump_req),
+ .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
+ COREDUMP_REJECT | COREDUMP_WAIT |
+ COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .size_ack = sizeof(struct coredump_ack),
+ .memory_types = cprm->memory_types,
+ .memory_types_mask = COREDUMP_MEMORY_ALL,
};
struct coredump_ack ack = {};
ssize_t usize;
@@ -873,6 +905,30 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
return false;
}
+ if (ack.mask & COREDUMP_MEMORY_TYPES) {
+ /* The memory types need the whole field. */
+ if (usize < COREDUMP_ACK_SIZE_VER1) {
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_MINSIZE);
+ return false;
+ }
+
+ /* The memory types only select what the kernel writes. */
+ if (!(ack.mask & COREDUMP_KERNEL)) {
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_CONFLICTING);
+ return false;
+ }
+
+ /* Refuse unknown memory types. */
+ if (ack.memory_types & ~req.memory_types_mask) {
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_UNSUPPORTED);
+ return false;
+ }
+ } else if (ack.memory_types) {
+ /* Like @spare the field must be zero when it isn't used. */
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_UNSUPPORTED);
+ return false;
+ }
+
/* Record header scratch; a bvec can't point at the stack. */
if (ack.mask & COREDUMP_RECORDS) {
cprm->record_hdr = kmalloc_obj(*cprm->record_hdr);
@@ -880,6 +936,10 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
return false;
}
+ /* The server's selection replaces the task's entirely. */
+ if (ack.mask & COREDUMP_MEMORY_TYPES)
+ cprm->memory_types = ack.memory_types;
+
cprm->mask = ack.mask;
return coredump_sock_mark(cprm->file, COREDUMP_MARK_REQACK);
}
@@ -1200,6 +1260,10 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm,
}
}
+#define COREDUMP_TASK_MEMORY_TYPES(mm) \
+ ((__mm_flags_get_word((mm)) & MMF_DUMP_FILTER_MASK) >> \
+ MMF_DUMP_FILTER_SHIFT)
+
void vfs_coredump(const kernel_siginfo_t *siginfo)
{
size_t *argv __free(kfree) = NULL;
@@ -1211,8 +1275,8 @@ void vfs_coredump(const kernel_siginfo_t *siginfo)
struct coredump_params cprm = {
.siginfo = siginfo,
.limit = rlimit(RLIMIT_CORE),
- /* Snapshot MMF_DUMP_FILTER_* (unlocked) and dumpable for the dump. */
- .mm_flags = __mm_flags_get_word(mm),
+ /* Snapshot the memory types (unlocked) and dumpable for the dump. */
+ .memory_types = COREDUMP_TASK_MEMORY_TYPES(mm),
.dumpable = task_exec_state_get_dumpable(current),
.vma_meta = NULL,
.cpu = raw_smp_processor_id(),
@@ -1746,15 +1810,15 @@ static bool always_dump_vma(struct vm_area_struct *vma)
}
#define DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER 1
+#define COREDUMP_MEMORY_TYPE_INCLUDE(types, type) \
+ ((types) & COREDUMP_MEMORY_##type)
/*
* Decide how much of @vma's contents should be included in a core dump.
*/
static unsigned long vma_dump_size(struct vm_area_struct *vma,
- unsigned long mm_flags)
+ u64 memory_types)
{
-#define FILTER(type) (mm_flags & (1UL << MMF_DUMP_##type))
-
/* always dump the vdso and vsyscall sections */
if (always_dump_vma(vma))
goto whole;
@@ -1764,18 +1828,22 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
/* support for DAX */
if (vma_is_dax(vma)) {
- if ((vma->vm_flags & VM_SHARED) && FILTER(DAX_SHARED))
+ if ((vma->vm_flags & VM_SHARED) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, DAX_SHARED))
goto whole;
- if (!(vma->vm_flags & VM_SHARED) && FILTER(DAX_PRIVATE))
+ if (!(vma->vm_flags & VM_SHARED) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, DAX_PRIVATE))
goto whole;
return 0;
}
/* Hugetlb memory check */
if (is_vm_hugetlb_page(vma)) {
- if ((vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_SHARED))
+ if ((vma->vm_flags & VM_SHARED) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, HUGETLB_SHARED))
goto whole;
- if (!(vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_PRIVATE))
+ if (!(vma->vm_flags & VM_SHARED) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, HUGETLB_PRIVATE))
goto whole;
return 0;
}
@@ -1787,25 +1855,27 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
/* By default, dump shared memory if mapped from an anonymous file. */
if (vma->vm_flags & VM_SHARED) {
if (file_inode(vma->vm_file)->i_nlink == 0 ?
- FILTER(ANON_SHARED) : FILTER(MAPPED_SHARED))
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ANON_SHARED) :
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, FILE_SHARED))
goto whole;
return 0;
}
/* Dump segments that have been written to. */
- if ((!IS_ENABLED(CONFIG_MMU) || vma->anon_vma) && FILTER(ANON_PRIVATE))
+ if ((!IS_ENABLED(CONFIG_MMU) || vma->anon_vma) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ANON_PRIVATE))
goto whole;
if (vma->vm_file == NULL)
return 0;
- if (FILTER(MAPPED_PRIVATE))
+ if (COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, FILE_PRIVATE))
goto whole;
/*
* If this is the beginning of an executable file mapping,
* dump the first page to aid in determining what was mapped here.
*/
- if (FILTER(ELF_HEADERS) &&
+ if (COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ELF_HEADERS) &&
vma->vm_pgoff == 0 && (vma->vm_flags & VM_READ)) {
if ((READ_ONCE(file_inode(vma->vm_file)->i_mode) & 0111) != 0)
return PAGE_SIZE;
@@ -1821,8 +1891,6 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
return DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER;
}
-#undef FILTER
-
return 0;
whole:
@@ -1907,7 +1975,7 @@ static bool dump_vma_snapshot(struct coredump_params *cprm)
m->start = vma->vm_start;
m->end = vma->vm_end;
m->flags = vma->vm_flags;
- m->dump_size = vma_dump_size(vma, cprm->mm_flags);
+ m->dump_size = vma_dump_size(vma, cprm->memory_types);
m->pgoff = vma->vm_pgoff;
m->file = vma->vm_file;
if (m->file)
diff --git a/include/linux/coredump.h b/include/linux/coredump.h
index b252bb2843b33..74af57b9406b8 100644
--- a/include/linux/coredump.h
+++ b/include/linux/coredump.h
@@ -32,8 +32,8 @@ struct coredump_params {
const kernel_siginfo_t *siginfo;
struct file *file;
unsigned long limit;
- /* MMF_DUMP_FILTER_* bits, snapshot of mm->flags at dump start. */
- unsigned long mm_flags;
+ /* COREDUMP_MEMORY_* types to dump, the task's or the server's. */
+ u64 memory_types;
/* Snapshot of dumpable at dump start. */
enum task_dumpable dumpable;
int cpu;
diff --git a/include/uapi/linux/coredump.h b/include/uapi/linux/coredump.h
index f3771861ca485..6d0c53b534ea8 100644
--- a/include/uapi/linux/coredump.h
+++ b/include/uapi/linux/coredump.h
@@ -16,6 +16,9 @@
* requires COREDUMP_KERNEL
* @COREDUMP_SPARSE: describe the holes in the coredump as zero records
* instead of transferring them; requires COREDUMP_RECORDS
+ * @COREDUMP_MEMORY_TYPES: dump the memory types in
+ * coredump_ack->memory_types instead of the ones
+ * the task selected; requires COREDUMP_KERNEL
*/
enum {
COREDUMP_KERNEL = (1ULL << 0),
@@ -24,6 +27,37 @@ enum {
COREDUMP_WAIT = (1ULL << 3),
COREDUMP_RECORDS = (1ULL << 4),
COREDUMP_SPARSE = (1ULL << 5),
+ COREDUMP_MEMORY_TYPES = (1ULL << 6),
+};
+
+/**
+ * coredump memory types
+ * @COREDUMP_MEMORY_ANON_PRIVATE: anonymous private memory
+ * @COREDUMP_MEMORY_ANON_SHARED: anonymous shared memory
+ * @COREDUMP_MEMORY_FILE_PRIVATE: file-backed private memory
+ * @COREDUMP_MEMORY_FILE_SHARED: file-backed shared memory
+ * @COREDUMP_MEMORY_ELF_HEADERS: the first page of a file-backed private
+ * mapping that starts an ELF file
+ * @COREDUMP_MEMORY_HUGETLB_PRIVATE: hugetlb private memory
+ * @COREDUMP_MEMORY_HUGETLB_SHARED: hugetlb shared memory
+ * @COREDUMP_MEMORY_DAX_PRIVATE: DAX private memory
+ * @COREDUMP_MEMORY_DAX_SHARED: DAX shared memory
+ *
+ * A bitmask of memory types a coredump may request to be included. New
+ * memory type bits must ensure that they do not steal memory from an
+ * existing one so a coredump server will continue to get the same
+ * coredumps even if a new bit is introduced.
+ */
+enum {
+ COREDUMP_MEMORY_ANON_PRIVATE = (1ULL << 0),
+ COREDUMP_MEMORY_ANON_SHARED = (1ULL << 1),
+ COREDUMP_MEMORY_FILE_PRIVATE = (1ULL << 2),
+ COREDUMP_MEMORY_FILE_SHARED = (1ULL << 3),
+ COREDUMP_MEMORY_ELF_HEADERS = (1ULL << 4),
+ COREDUMP_MEMORY_HUGETLB_PRIVATE = (1ULL << 5),
+ COREDUMP_MEMORY_HUGETLB_SHARED = (1ULL << 6),
+ COREDUMP_MEMORY_DAX_PRIVATE = (1ULL << 7),
+ COREDUMP_MEMORY_DAX_SHARED = (1ULL << 8),
};
/**
@@ -31,6 +65,8 @@ enum {
* @size: size of struct coredump_req
* @size_ack: known size of struct coredump_ack on this kernel
* @mask: supported features
+ * @memory_types: the memory types the task selected
+ * @memory_types_mask: the memory types this kernel knows
*
* When a coredump happens the kernel will connect to the coredump
* socket and send a coredump request to the coredump server. The @size
@@ -49,15 +85,27 @@ enum {
* struct coredump_ack the kernel knows. Userspace may only send up to
* coredump_req->size_ack bytes to the kernel and must set
* coredump_ack->size accordingly.
+ *
+ * @memory_types is set to the default memory types that are included in
+ * the coredump. This can be overridden by raising bits in
+ * coredump_ack->memory_types.
+ *
+ * @memory_types_mask contains a bitmask of all memory types the kernel
+ * knows about. A coredump server may only raise bits in
+ * coredump_ack->memory_types that are raised in
+ * coredump_req->memory_types_mask.
*/
struct coredump_req {
__u32 size;
__u32 size_ack;
__u64 mask;
+ __u64 memory_types;
+ __u64 memory_types_mask;
};
enum {
COREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */
+ COREDUMP_REQ_SIZE_VER1 = 32U, /* memory_types and memory_types_mask added */
};
/**
@@ -65,6 +113,8 @@ enum {
* @size: size of the struct
* @spare: unused
* @mask: features kernel is supposed to use
+ * @memory_types: memory types to dump, only with COREDUMP_MEMORY_TYPES
+ * in @mask
*
* The @size member must be set to the size of struct coredump_ack. It
* may never exceed what the kernel returned in coredump_req->size_ack
@@ -74,15 +124,30 @@ enum {
* The @mask member must be set to the features the coredump server
* wants the kernel to use. Only bits the kernel returned in
* coredump_req->mask may be set.
+ *
+ * If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the
+ * memory types set in the @memory_types mask. Zero is valid and dumps
+ * no memory apart from the mappings that are always dumped.
+ *
+ * Note that memory a task excluded via MADV_DONTDUMP is always left
+ * out. A coredump server wanting to add or drop memory types instead of
+ * outright replacing it should simply copy coredump_req->memory_types
+ * and then mask off or raise types as needed.
+ *
+ * Note that @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't
+ * raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of
+ * at least COREDUMP_ACK_SIZE_VER1 bytes.
*/
struct coredump_ack {
__u32 size;
__u32 spare;
__u64 mask;
+ __u64 memory_types;
};
enum {
COREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */
+ COREDUMP_ACK_SIZE_VER1 = 24U, /* memory_types added */
};
/**
@@ -90,11 +155,12 @@ enum {
*
* The kernel will place a single byte on the coredump socket. The
* markers notify userspace whether the coredump ack succeeded or
- * failed.
+ * failed. After any marker other than COREDUMP_MARK_REQACK the kernel
+ * closes the connection and no coredump is generated.
*
* @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small
* @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big
- * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid
+ * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask or memory types were invalid
* @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options
* @COREDUMP_MARK_REQACK: the coredump request and ack was successful
* @__COREDUMP_MARK_MAX: the maximum coredump mark value
diff --git a/tools/include/uapi/linux/coredump.h b/tools/include/uapi/linux/coredump.h
index f3771861ca485..6d0c53b534ea8 100644
--- a/tools/include/uapi/linux/coredump.h
+++ b/tools/include/uapi/linux/coredump.h
@@ -16,6 +16,9 @@
* requires COREDUMP_KERNEL
* @COREDUMP_SPARSE: describe the holes in the coredump as zero records
* instead of transferring them; requires COREDUMP_RECORDS
+ * @COREDUMP_MEMORY_TYPES: dump the memory types in
+ * coredump_ack->memory_types instead of the ones
+ * the task selected; requires COREDUMP_KERNEL
*/
enum {
COREDUMP_KERNEL = (1ULL << 0),
@@ -24,6 +27,37 @@ enum {
COREDUMP_WAIT = (1ULL << 3),
COREDUMP_RECORDS = (1ULL << 4),
COREDUMP_SPARSE = (1ULL << 5),
+ COREDUMP_MEMORY_TYPES = (1ULL << 6),
+};
+
+/**
+ * coredump memory types
+ * @COREDUMP_MEMORY_ANON_PRIVATE: anonymous private memory
+ * @COREDUMP_MEMORY_ANON_SHARED: anonymous shared memory
+ * @COREDUMP_MEMORY_FILE_PRIVATE: file-backed private memory
+ * @COREDUMP_MEMORY_FILE_SHARED: file-backed shared memory
+ * @COREDUMP_MEMORY_ELF_HEADERS: the first page of a file-backed private
+ * mapping that starts an ELF file
+ * @COREDUMP_MEMORY_HUGETLB_PRIVATE: hugetlb private memory
+ * @COREDUMP_MEMORY_HUGETLB_SHARED: hugetlb shared memory
+ * @COREDUMP_MEMORY_DAX_PRIVATE: DAX private memory
+ * @COREDUMP_MEMORY_DAX_SHARED: DAX shared memory
+ *
+ * A bitmask of memory types a coredump may request to be included. New
+ * memory type bits must ensure that they do not steal memory from an
+ * existing one so a coredump server will continue to get the same
+ * coredumps even if a new bit is introduced.
+ */
+enum {
+ COREDUMP_MEMORY_ANON_PRIVATE = (1ULL << 0),
+ COREDUMP_MEMORY_ANON_SHARED = (1ULL << 1),
+ COREDUMP_MEMORY_FILE_PRIVATE = (1ULL << 2),
+ COREDUMP_MEMORY_FILE_SHARED = (1ULL << 3),
+ COREDUMP_MEMORY_ELF_HEADERS = (1ULL << 4),
+ COREDUMP_MEMORY_HUGETLB_PRIVATE = (1ULL << 5),
+ COREDUMP_MEMORY_HUGETLB_SHARED = (1ULL << 6),
+ COREDUMP_MEMORY_DAX_PRIVATE = (1ULL << 7),
+ COREDUMP_MEMORY_DAX_SHARED = (1ULL << 8),
};
/**
@@ -31,6 +65,8 @@ enum {
* @size: size of struct coredump_req
* @size_ack: known size of struct coredump_ack on this kernel
* @mask: supported features
+ * @memory_types: the memory types the task selected
+ * @memory_types_mask: the memory types this kernel knows
*
* When a coredump happens the kernel will connect to the coredump
* socket and send a coredump request to the coredump server. The @size
@@ -49,15 +85,27 @@ enum {
* struct coredump_ack the kernel knows. Userspace may only send up to
* coredump_req->size_ack bytes to the kernel and must set
* coredump_ack->size accordingly.
+ *
+ * @memory_types is set to the default memory types that are included in
+ * the coredump. This can be overridden by raising bits in
+ * coredump_ack->memory_types.
+ *
+ * @memory_types_mask contains a bitmask of all memory types the kernel
+ * knows about. A coredump server may only raise bits in
+ * coredump_ack->memory_types that are raised in
+ * coredump_req->memory_types_mask.
*/
struct coredump_req {
__u32 size;
__u32 size_ack;
__u64 mask;
+ __u64 memory_types;
+ __u64 memory_types_mask;
};
enum {
COREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */
+ COREDUMP_REQ_SIZE_VER1 = 32U, /* memory_types and memory_types_mask added */
};
/**
@@ -65,6 +113,8 @@ enum {
* @size: size of the struct
* @spare: unused
* @mask: features kernel is supposed to use
+ * @memory_types: memory types to dump, only with COREDUMP_MEMORY_TYPES
+ * in @mask
*
* The @size member must be set to the size of struct coredump_ack. It
* may never exceed what the kernel returned in coredump_req->size_ack
@@ -74,15 +124,30 @@ enum {
* The @mask member must be set to the features the coredump server
* wants the kernel to use. Only bits the kernel returned in
* coredump_req->mask may be set.
+ *
+ * If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the
+ * memory types set in the @memory_types mask. Zero is valid and dumps
+ * no memory apart from the mappings that are always dumped.
+ *
+ * Note that memory a task excluded via MADV_DONTDUMP is always left
+ * out. A coredump server wanting to add or drop memory types instead of
+ * outright replacing it should simply copy coredump_req->memory_types
+ * and then mask off or raise types as needed.
+ *
+ * Note that @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't
+ * raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of
+ * at least COREDUMP_ACK_SIZE_VER1 bytes.
*/
struct coredump_ack {
__u32 size;
__u32 spare;
__u64 mask;
+ __u64 memory_types;
};
enum {
COREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */
+ COREDUMP_ACK_SIZE_VER1 = 24U, /* memory_types added */
};
/**
@@ -90,11 +155,12 @@ enum {
*
* The kernel will place a single byte on the coredump socket. The
* markers notify userspace whether the coredump ack succeeded or
- * failed.
+ * failed. After any marker other than COREDUMP_MARK_REQACK the kernel
+ * closes the connection and no coredump is generated.
*
* @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small
* @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big
- * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid
+ * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask or memory types were invalid
* @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options
* @COREDUMP_MARK_REQACK: the coredump request and ack was successful
* @__COREDUMP_MARK_MAX: the maximum coredump mark value
diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
index daff908232a22..f5c9bad875463 100644
--- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
+++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
@@ -508,91 +508,89 @@ TEST_F(coredump, socket_request_reject)
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-TEST_F(coredump, socket_request_invalid_flag_combination)
+/* An ack the kernel must refuse and how. */
+struct refused_ack {
+ /* The ack, and how many bytes of it the server sends before it hangs up. */
+ struct coredump_ack ack;
+ size_t bytes;
+ /* The marker the kernel answers with, or none if @no_marker. */
+ enum coredump_mark mark;
+ bool no_marker;
+};
+
+/* Send @refused, expect the kernel to refuse it and hang up. */
+static void check_refused_ack(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(coredump) *self,
+ const struct refused_ack *refused)
{
- int pidfd, ret, status;
+ int pidfd, status;
pid_t pid, pid_coredump_server;
struct pidfd_info info = {};
int ipc_sockets[2];
char c;
+ ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
- ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
- ASSERT_EQ(ret, 0);
-
pid_coredump_server = fork();
ASSERT_GE(pid_coredump_server, 0);
if (pid_coredump_server == 0) {
- struct coredump_req req = {};
int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
int exit_code = EXIT_FAILURE;
+ struct coredump_req req = {};
close(ipc_sockets[0]);
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0) {
- fprintf(stderr, "socket_request_invalid_flag_combination: create_and_listen_unix_socket failed: %m\n");
+ if (fd_server < 0)
goto out;
- }
- if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_request_invalid_flag_combination: write_nointr to ipc socket failed: %m\n");
+ if (write_nointr(ipc_sockets[1], "1", 1) < 0)
goto out;
- }
close(ipc_sockets[1]);
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_invalid_flag_combination: accept4 failed: %m\n");
+ if (fd_coredump < 0)
goto out;
- }
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_request_invalid_flag_combination: get_peer_pidfd failed\n");
+ if (fd_peer_pidfd < 0)
goto out;
- }
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: get_pidfd_info failed\n");
+ /* The task shows as dumping while it waits for the ack. */
+ if (!get_pidfd_info(fd_peer_pidfd, &info))
goto out;
- }
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: PIDFD_INFO_COREDUMP not set in mask\n");
+ if (!(info.mask & PIDFD_INFO_COREDUMP) ||
+ !(info.coredump_mask & PIDFD_COREDUMPED)) {
+ fprintf(stderr, "Peer isn't marked as dumping\n");
goto out;
}
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: PIDFD_COREDUMPED not set in coredump_mask\n");
+ if (!read_coredump_req(fd_coredump, &req))
goto out;
- }
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: read_coredump_req failed\n");
+ if (!check_coredump_req(&req))
goto out;
- }
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: check_coredump_req failed\n");
+ if (!send_coredump_ack_bytes(fd_coredump, &refused->ack,
+ refused->bytes))
goto out;
- }
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_KERNEL | COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: send_coredump_ack failed\n");
+ /* Nothing more to say. A server that died looks the same. */
+ if (shutdown(fd_coredump, SHUT_WR))
goto out;
- }
- if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: read_marker COREDUMP_MARK_CONFLICTING failed\n");
+ if (!refused->no_marker &&
+ !read_marker(fd_coredump, refused->mark))
+ goto out;
+
+ /* The kernel hangs up after a refusal, marker or not. */
+ if (!read_hangup(fd_coredump))
goto out;
- }
exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_invalid_flag_combination: completed successfully\n");
out:
if (fd_peer_pidfd >= 0)
close(fd_peer_pidfd);
@@ -627,7 +625,83 @@ TEST_F(coredump, socket_request_invalid_flag_combination)
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
+/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */
+static void check_conflicting_ack(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(coredump) *self, __u64 ack_mask)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = ack_mask,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_CONFLICTING,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* More than one of KERNEL, USERSPACE and REJECT. */
+TEST_F(coredump, socket_request_invalid_flag_combination)
+{
+ check_conflicting_ack(_metadata, self,
+ COREDUMP_KERNEL | COREDUMP_REJECT | COREDUMP_WAIT);
+}
+
+/* A flag the kernel didn't advertise in coredump_req->mask. */
TEST_F(coredump, socket_request_unknown_flag)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = 1ULL << 63,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_UNSUPPORTED,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* An ack smaller than the first published struct. */
+TEST_F(coredump, socket_request_invalid_size_small)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = COREDUMP_ACK_SIZE_VER0 / 2,
+ .mask = COREDUMP_REJECT | COREDUMP_WAIT,
+ },
+ .bytes = COREDUMP_ACK_SIZE_VER0 / 2,
+ .mark = COREDUMP_MARK_MINSIZE,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* An ack bigger than the kernel said it accepts. */
+TEST_F(coredump, socket_request_invalid_size_large)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE,
+ .mask = COREDUMP_REJECT | COREDUMP_WAIT,
+ },
+ .bytes = COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE,
+ .mark = COREDUMP_MARK_MAXSIZE,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/*
+ * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGSEGV
+ *
+ * Verify that when using socket-based coredump protocol,
+ * the coredump_signal field is correctly exposed as SIGSEGV.
+ * Also check that the coredump_code field is correctly exposed
+ * as SEGV_MAPERR.
+ */
+TEST_F(coredump, socket_coredump_signal_sigsegv)
{
int pidfd, ret, status;
pid_t pid, pid_coredump_server;
@@ -651,12 +725,12 @@ TEST_F(coredump, socket_request_unknown_flag)
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
if (fd_server < 0) {
- fprintf(stderr, "socket_request_unknown_flag: create_and_listen_unix_socket failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: create_and_listen_unix_socket failed: %m\n");
goto out;
}
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_request_unknown_flag: write_nointr to ipc socket failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: write_nointr to ipc socket failed: %m\n");
goto out;
}
@@ -664,53 +738,73 @@ TEST_F(coredump, socket_request_unknown_flag)
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_unknown_flag: accept4 failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: accept4 failed: %m\n");
goto out;
}
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_request_unknown_flag: get_peer_pidfd failed\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: get_peer_pidfd failed\n");
goto out;
}
if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_unknown_flag: get_pidfd_info failed\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: get_pidfd_info failed\n");
goto out;
}
if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_request_unknown_flag: PIDFD_INFO_COREDUMP not set in mask\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP not set in mask\n");
goto out;
}
if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_unknown_flag: PIDFD_COREDUMPED not set in coredump_mask\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_COREDUMPED not set in coredump_mask\n");
goto out;
}
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_unknown_flag: read_coredump_req failed\n");
+ /* Verify coredump_signal is available and correct */
+ if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
goto out;
}
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_unknown_flag: check_coredump_req failed\n");
+ if (info.coredump_signal != SIGSEGV) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_signal=%d, expected SIGSEGV=%d\n",
+ info.coredump_signal, SIGSEGV);
+ goto out;
+ }
+
+ /* Verify coredump_code is available and correct */
+ if (!(info.mask & PIDFD_INFO_COREDUMP_CODE)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_CODE not set in mask\n");
+ goto out;
+ }
+
+ if (info.coredump_code != SEGV_MAPERR) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_code=%d, expected SEGV_MAPERR=%d\n",
+ info.coredump_code, SEGV_MAPERR);
goto out;
}
- if (!send_coredump_ack(fd_coredump, &req, (1ULL << 63), 0)) {
- fprintf(stderr, "socket_request_unknown_flag: send_coredump_ack failed\n");
+ if (!read_coredump_req(fd_coredump, &req)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: read_coredump_req failed\n");
+ goto out;
+ }
+
+ if (!send_coredump_ack(fd_coredump, &req,
+ COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: send_coredump_ack failed\n");
goto out;
}
- if (!read_marker(fd_coredump, COREDUMP_MARK_UNSUPPORTED)) {
- fprintf(stderr, "socket_request_unknown_flag: read_marker COREDUMP_MARK_UNSUPPORTED failed\n");
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: read_marker COREDUMP_MARK_REQACK failed\n");
goto out;
}
exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_unknown_flag: completed successfully\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: completed successfully\n");
out:
if (fd_peer_pidfd >= 0)
close(fd_peer_pidfd);
@@ -736,16 +830,27 @@ TEST_F(coredump, socket_request_unknown_flag)
waitpid(pid, &status, 0);
ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
+ ASSERT_EQ(WTERMSIG(status), SIGSEGV);
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
+ ASSERT_EQ(info.coredump_signal, SIGSEGV);
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_CODE));
+ ASSERT_EQ(info.coredump_code, SEGV_MAPERR);
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-TEST_F(coredump, socket_request_invalid_size_small)
+/*
+ * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGABRT
+ *
+ * Verify that when using socket-based coredump protocol,
+ * the coredump_signal field is correctly exposed as SIGABRT.
+ * Also check that the coredump_code field is correctly exposed
+ * as SI_TKILL.
+ */
+TEST_F(coredump, socket_coredump_signal_sigabrt)
{
int pidfd, ret, status;
pid_t pid, pid_coredump_server;
@@ -769,12 +874,12 @@ TEST_F(coredump, socket_request_invalid_size_small)
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
if (fd_server < 0) {
- fprintf(stderr, "socket_request_invalid_size_small: create_and_listen_unix_socket failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: create_and_listen_unix_socket failed: %m\n");
goto out;
}
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_request_invalid_size_small: write_nointr to ipc socket failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: write_nointr to ipc socket failed: %m\n");
goto out;
}
@@ -782,55 +887,67 @@ TEST_F(coredump, socket_request_invalid_size_small)
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_invalid_size_small: accept4 failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: accept4 failed: %m\n");
goto out;
}
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_request_invalid_size_small: get_peer_pidfd failed\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: get_peer_pidfd failed\n");
goto out;
}
if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_invalid_size_small: get_pidfd_info failed\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: get_pidfd_info failed\n");
goto out;
}
if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_request_invalid_size_small: PIDFD_INFO_COREDUMP not set in mask\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP not set in mask\n");
goto out;
}
if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_invalid_size_small: PIDFD_COREDUMPED not set in coredump_mask\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_COREDUMPED not set in coredump_mask\n");
goto out;
}
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_invalid_size_small: read_coredump_req failed\n");
+ /* Verify coredump_signal is available and correct */
+ if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
goto out;
}
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_invalid_size_small: check_coredump_req failed\n");
+ if (info.coredump_signal != SIGABRT) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_signal=%d, expected SIGABRT=%d\n",
+ info.coredump_signal, SIGABRT);
+ goto out;
+ }
+
+ if (info.coredump_code != SI_TKILL) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_code=%d, expected SI_TKILL=%d\n",
+ info.coredump_code, SI_TKILL);
+ goto out;
+ }
+
+ if (!read_coredump_req(fd_coredump, &req)) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: read_coredump_req failed\n");
goto out;
}
if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_REJECT | COREDUMP_WAIT,
- COREDUMP_ACK_SIZE_VER0 / 2)) {
- fprintf(stderr, "socket_request_invalid_size_small: send_coredump_ack failed\n");
+ COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: send_coredump_ack failed\n");
goto out;
}
- if (!read_marker(fd_coredump, COREDUMP_MARK_MINSIZE)) {
- fprintf(stderr, "socket_request_invalid_size_small: read_marker COREDUMP_MARK_MINSIZE failed\n");
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: read_marker COREDUMP_MARK_REQACK failed\n");
goto out;
}
exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_invalid_size_small: completed successfully\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: completed successfully\n");
out:
if (fd_peer_pidfd >= 0)
close(fd_peer_pidfd);
@@ -849,513 +966,104 @@ TEST_F(coredump, socket_request_invalid_size_small)
pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0)
- crashing_child();
+ abort();
pidfd = sys_pidfd_open(pid, 0);
ASSERT_GE(pidfd, 0);
waitpid(pid, &status, 0);
ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
+ ASSERT_EQ(WTERMSIG(status), SIGABRT);
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
+ ASSERT_EQ(info.coredump_signal, SIGABRT);
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_CODE));
+ ASSERT_EQ(info.coredump_code, SI_TKILL);
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-TEST_F(coredump, socket_request_invalid_size_large)
+TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps, 500)
{
- int pidfd, ret, status;
- pid_t pid, pid_coredump_server;
+ int pidfd[NUM_CRASHING_COREDUMPS], status[NUM_CRASHING_COREDUMPS];
+ pid_t pid[NUM_CRASHING_COREDUMPS], pid_coredump_server;
struct pidfd_info info = {};
int ipc_sockets[2];
char c;
ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
- ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
- ASSERT_EQ(ret, 0);
+ ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
pid_coredump_server = fork();
ASSERT_GE(pid_coredump_server, 0);
if (pid_coredump_server == 0) {
- struct coredump_req req = {};
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
+ int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;
int exit_code = EXIT_FAILURE;
+ struct coredump_req req = {};
close(ipc_sockets[0]);
-
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
if (fd_server < 0) {
- fprintf(stderr, "socket_request_invalid_size_large: create_and_listen_unix_socket failed: %m\n");
+ fprintf(stderr, "Failed to create and listen on unix socket\n");
goto out;
}
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_request_invalid_size_large: write_nointr to ipc socket failed: %m\n");
+ fprintf(stderr, "Failed to notify parent via ipc socket\n");
goto out;
}
-
close(ipc_sockets[1]);
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_invalid_size_large: accept4 failed: %m\n");
- goto out;
- }
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_request_invalid_size_large: get_peer_pidfd failed\n");
- goto out;
- }
+ for (int i = 0; i < NUM_CRASHING_COREDUMPS; i++) {
+ fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
+ if (fd_coredump < 0) {
+ fprintf(stderr, "accept4 failed: %m\n");
+ goto out;
+ }
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_invalid_size_large: get_pidfd_info failed\n");
- goto out;
- }
+ fd_peer_pidfd = get_peer_pidfd(fd_coredump);
+ if (fd_peer_pidfd < 0) {
+ fprintf(stderr, "get_peer_pidfd failed for fd %d: %m\n", fd_coredump);
+ goto out;
+ }
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_request_invalid_size_large: PIDFD_INFO_COREDUMP not set in mask\n");
- goto out;
- }
+ if (!get_pidfd_info(fd_peer_pidfd, &info)) {
+ fprintf(stderr, "get_pidfd_info failed for fd %d\n", fd_peer_pidfd);
+ goto out;
+ }
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_invalid_size_large: PIDFD_COREDUMPED not set in coredump_mask\n");
- goto out;
- }
+ if (!(info.mask & PIDFD_INFO_COREDUMP)) {
+ fprintf(stderr, "pidfd info missing PIDFD_INFO_COREDUMP for fd %d\n", fd_peer_pidfd);
+ goto out;
+ }
+ if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
+ fprintf(stderr, "pidfd info missing PIDFD_COREDUMPED for fd %d\n", fd_peer_pidfd);
+ goto out;
+ }
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_invalid_size_large: read_coredump_req failed\n");
- goto out;
- }
+ if (!read_coredump_req(fd_coredump, &req)) {
+ fprintf(stderr, "read_coredump_req failed for fd %d\n", fd_coredump);
+ goto out;
+ }
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_invalid_size_large: check_coredump_req failed\n");
- goto out;
- }
+ if (!check_coredump_req(&req)) {
+ fprintf(stderr, "check_coredump_req failed for fd %d\n", fd_coredump);
+ goto out;
+ }
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_REJECT | COREDUMP_WAIT,
- COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE)) {
- fprintf(stderr, "socket_request_invalid_size_large: send_coredump_ack failed\n");
- goto out;
- }
+ if (!send_coredump_ack(fd_coredump, &req,
+ COREDUMP_KERNEL | COREDUMP_WAIT, 0)) {
+ fprintf(stderr, "send_coredump_ack failed for fd %d\n", fd_coredump);
+ goto out;
+ }
- if (!read_marker(fd_coredump, COREDUMP_MARK_MAXSIZE)) {
- fprintf(stderr, "socket_request_invalid_size_large: read_marker COREDUMP_MARK_MAXSIZE failed\n");
- goto out;
- }
-
- exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_invalid_size_large: completed successfully\n");
-out:
- if (fd_peer_pidfd >= 0)
- close(fd_peer_pidfd);
- if (fd_coredump >= 0)
- close(fd_coredump);
- if (fd_server >= 0)
- close(fd_server);
- _exit(exit_code);
- }
- self->pid_coredump_server = pid_coredump_server;
-
- EXPECT_EQ(close(ipc_sockets[1]), 0);
- ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
- EXPECT_EQ(close(ipc_sockets[0]), 0);
-
- pid = fork();
- ASSERT_GE(pid, 0);
- if (pid == 0)
- crashing_child();
-
- pidfd = sys_pidfd_open(pid, 0);
- ASSERT_GE(pidfd, 0);
-
- waitpid(pid, &status, 0);
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
-
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
-
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
-}
-
-/*
- * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGSEGV
- *
- * Verify that when using socket-based coredump protocol,
- * the coredump_signal field is correctly exposed as SIGSEGV.
- * Also check that the coredump_code field is correctly exposed
- * as SEGV_MAPERR.
- */
-TEST_F(coredump, socket_coredump_signal_sigsegv)
-{
- int pidfd, ret, status;
- pid_t pid, pid_coredump_server;
- struct pidfd_info info = {};
- int ipc_sockets[2];
- char c;
-
- ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
-
- ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
- ASSERT_EQ(ret, 0);
-
- pid_coredump_server = fork();
- ASSERT_GE(pid_coredump_server, 0);
- if (pid_coredump_server == 0) {
- struct coredump_req req = {};
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
- int exit_code = EXIT_FAILURE;
-
- close(ipc_sockets[0]);
-
- fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: create_and_listen_unix_socket failed: %m\n");
- goto out;
- }
-
- if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: write_nointr to ipc socket failed: %m\n");
- goto out;
- }
-
- close(ipc_sockets[1]);
-
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: accept4 failed: %m\n");
- goto out;
- }
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: get_peer_pidfd failed\n");
- goto out;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: get_pidfd_info failed\n");
- goto out;
- }
-
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP not set in mask\n");
- goto out;
- }
-
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_COREDUMPED not set in coredump_mask\n");
- goto out;
- }
-
- /* Verify coredump_signal is available and correct */
- if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
- goto out;
- }
-
- if (info.coredump_signal != SIGSEGV) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_signal=%d, expected SIGSEGV=%d\n",
- info.coredump_signal, SIGSEGV);
- goto out;
- }
-
- /* Verify coredump_code is available and correct */
- if (!(info.mask & PIDFD_INFO_COREDUMP_CODE)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_CODE not set in mask\n");
- goto out;
- }
-
- if (info.coredump_code != SEGV_MAPERR) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_code=%d, expected SEGV_MAPERR=%d\n",
- info.coredump_code, SEGV_MAPERR);
- goto out;
- }
-
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: read_coredump_req failed\n");
- goto out;
- }
-
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: send_coredump_ack failed\n");
- goto out;
- }
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: read_marker COREDUMP_MARK_REQACK failed\n");
- goto out;
- }
-
- exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_coredump_signal_sigsegv: completed successfully\n");
-out:
- if (fd_peer_pidfd >= 0)
- close(fd_peer_pidfd);
- if (fd_coredump >= 0)
- close(fd_coredump);
- if (fd_server >= 0)
- close(fd_server);
- _exit(exit_code);
- }
- self->pid_coredump_server = pid_coredump_server;
-
- EXPECT_EQ(close(ipc_sockets[1]), 0);
- ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
- EXPECT_EQ(close(ipc_sockets[0]), 0);
-
- pid = fork();
- ASSERT_GE(pid, 0);
- if (pid == 0)
- crashing_child();
-
- pidfd = sys_pidfd_open(pid, 0);
- ASSERT_GE(pidfd, 0);
-
- waitpid(pid, &status, 0);
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_EQ(WTERMSIG(status), SIGSEGV);
-
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
- ASSERT_EQ(info.coredump_signal, SIGSEGV);
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_CODE));
- ASSERT_EQ(info.coredump_code, SEGV_MAPERR);
-
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
-}
-
-/*
- * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGABRT
- *
- * Verify that when using socket-based coredump protocol,
- * the coredump_signal field is correctly exposed as SIGABRT.
- * Also check that the coredump_code field is correctly exposed
- * as SI_TKILL.
- */
-TEST_F(coredump, socket_coredump_signal_sigabrt)
-{
- int pidfd, ret, status;
- pid_t pid, pid_coredump_server;
- struct pidfd_info info = {};
- int ipc_sockets[2];
- char c;
-
- ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
-
- ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
- ASSERT_EQ(ret, 0);
-
- pid_coredump_server = fork();
- ASSERT_GE(pid_coredump_server, 0);
- if (pid_coredump_server == 0) {
- struct coredump_req req = {};
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
- int exit_code = EXIT_FAILURE;
-
- close(ipc_sockets[0]);
-
- fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: create_and_listen_unix_socket failed: %m\n");
- goto out;
- }
-
- if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: write_nointr to ipc socket failed: %m\n");
- goto out;
- }
-
- close(ipc_sockets[1]);
-
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: accept4 failed: %m\n");
- goto out;
- }
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: get_peer_pidfd failed\n");
- goto out;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: get_pidfd_info failed\n");
- goto out;
- }
-
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP not set in mask\n");
- goto out;
- }
-
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_COREDUMPED not set in coredump_mask\n");
- goto out;
- }
-
- /* Verify coredump_signal is available and correct */
- if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
- goto out;
- }
-
- if (info.coredump_signal != SIGABRT) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_signal=%d, expected SIGABRT=%d\n",
- info.coredump_signal, SIGABRT);
- goto out;
- }
-
- if (info.coredump_code != SI_TKILL) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_code=%d, expected SI_TKILL=%d\n",
- info.coredump_code, SI_TKILL);
- goto out;
- }
-
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: read_coredump_req failed\n");
- goto out;
- }
-
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: send_coredump_ack failed\n");
- goto out;
- }
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: read_marker COREDUMP_MARK_REQACK failed\n");
- goto out;
- }
-
- exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_coredump_signal_sigabrt: completed successfully\n");
-out:
- if (fd_peer_pidfd >= 0)
- close(fd_peer_pidfd);
- if (fd_coredump >= 0)
- close(fd_coredump);
- if (fd_server >= 0)
- close(fd_server);
- _exit(exit_code);
- }
- self->pid_coredump_server = pid_coredump_server;
-
- EXPECT_EQ(close(ipc_sockets[1]), 0);
- ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
- EXPECT_EQ(close(ipc_sockets[0]), 0);
-
- pid = fork();
- ASSERT_GE(pid, 0);
- if (pid == 0)
- abort();
-
- pidfd = sys_pidfd_open(pid, 0);
- ASSERT_GE(pidfd, 0);
-
- waitpid(pid, &status, 0);
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_EQ(WTERMSIG(status), SIGABRT);
-
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
- ASSERT_EQ(info.coredump_signal, SIGABRT);
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_CODE));
- ASSERT_EQ(info.coredump_code, SI_TKILL);
-
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
-}
-
-TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps, 500)
-{
- int pidfd[NUM_CRASHING_COREDUMPS], status[NUM_CRASHING_COREDUMPS];
- pid_t pid[NUM_CRASHING_COREDUMPS], pid_coredump_server;
- struct pidfd_info info = {};
- int ipc_sockets[2];
- char c;
-
- ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
-
- ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
-
- pid_coredump_server = fork();
- ASSERT_GE(pid_coredump_server, 0);
- if (pid_coredump_server == 0) {
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;
- int exit_code = EXIT_FAILURE;
- struct coredump_req req = {};
-
- close(ipc_sockets[0]);
- fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0) {
- fprintf(stderr, "Failed to create and listen on unix socket\n");
- goto out;
- }
-
- if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "Failed to notify parent via ipc socket\n");
- goto out;
- }
- close(ipc_sockets[1]);
-
- for (int i = 0; i < NUM_CRASHING_COREDUMPS; i++) {
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "accept4 failed: %m\n");
- goto out;
- }
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "get_peer_pidfd failed for fd %d: %m\n", fd_coredump);
- goto out;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "get_pidfd_info failed for fd %d\n", fd_peer_pidfd);
- goto out;
- }
-
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "pidfd info missing PIDFD_INFO_COREDUMP for fd %d\n", fd_peer_pidfd);
- goto out;
- }
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "pidfd info missing PIDFD_COREDUMPED for fd %d\n", fd_peer_pidfd);
- goto out;
- }
-
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "read_coredump_req failed for fd %d\n", fd_coredump);
- goto out;
- }
-
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "check_coredump_req failed for fd %d\n", fd_coredump);
- goto out;
- }
-
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_KERNEL | COREDUMP_WAIT, 0)) {
- fprintf(stderr, "send_coredump_ack failed for fd %d\n", fd_coredump);
- goto out;
- }
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
- fprintf(stderr, "read_marker failed for fd %d\n", fd_coredump);
- goto out;
- }
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
+ fprintf(stderr, "read_marker failed for fd %d\n", fd_coredump);
+ goto out;
+ }
fd_core_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
if (fd_core_file < 0) {
@@ -2015,113 +1723,27 @@ TEST_F(coredump, socket_request_sparse_blob_upload)
ASSERT_EQ(read_nointr(pipefds[0], &received, sizeof(received)),
sizeof(received));
ASSERT_EQ(read_nointr(pipefds[0], &coredump_size, sizeof(coredump_size)),
- sizeof(coredump_size));
- EXPECT_EQ(close(pipefds[0]), 0);
-
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
-
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
-
- /* The mapping is in the coredump, holes included. */
- ASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE);
-
- /* The object isn't sparse and doesn't carry them. */
- ASSERT_EQ(stat("/tmp/coredump.file", &st), 0);
- ASSERT_LT(st.st_size, coredump_size / 8);
-
- /* And a debugger still sees an ordinary ELF core file. */
- fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC);
- ASSERT_GE(fd_core_file, 0);
- ASSERT_TRUE(is_elf_core(fd_core_file));
- EXPECT_EQ(close(fd_core_file), 0);
-}
-
-/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */
-static void check_conflicting_ack(struct __test_metadata *const _metadata,
- FIXTURE_DATA(coredump) *self, __u64 ack_mask)
-{
- int pidfd, status;
- pid_t pid, pid_coredump_server;
- struct pidfd_info info = {};
- int ipc_sockets[2];
- char c;
-
- ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
- ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
-
- pid_coredump_server = fork();
- ASSERT_GE(pid_coredump_server, 0);
- if (pid_coredump_server == 0) {
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
- int exit_code = EXIT_FAILURE;
- struct coredump_req req = {};
-
- close(ipc_sockets[0]);
-
- fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0)
- goto out;
-
- if (write_nointr(ipc_sockets[1], "1", 1) < 0)
- goto out;
-
- close(ipc_sockets[1]);
-
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0)
- goto out;
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0)
- goto out;
-
- if (!read_coredump_req(fd_coredump, &req))
- goto out;
-
- if (!check_coredump_req(&req))
- goto out;
-
- if (!send_coredump_ack(fd_coredump, &req, ack_mask, 0))
- goto out;
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING))
- goto out;
-
- exit_code = EXIT_SUCCESS;
-out:
- if (fd_peer_pidfd >= 0)
- close(fd_peer_pidfd);
- if (fd_coredump >= 0)
- close(fd_coredump);
- if (fd_server >= 0)
- close(fd_server);
- _exit(exit_code);
- }
- self->pid_coredump_server = pid_coredump_server;
-
- EXPECT_EQ(close(ipc_sockets[1]), 0);
- ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
- EXPECT_EQ(close(ipc_sockets[0]), 0);
-
- pid = fork();
- ASSERT_GE(pid, 0);
- if (pid == 0)
- crashing_child();
-
- pidfd = sys_pidfd_open(pid, 0);
- ASSERT_GE(pidfd, 0);
-
- waitpid(pid, &status, 0);
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
+ sizeof(coredump_size));
+ EXPECT_EQ(close(pipefds[0]), 0);
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
+
+ /* The mapping is in the coredump, holes included. */
+ ASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE);
+
+ /* The object isn't sparse and doesn't carry them. */
+ ASSERT_EQ(stat("/tmp/coredump.file", &st), 0);
+ ASSERT_LT(st.st_size, coredump_size / 8);
+
+ /* And a debugger still sees an ordinary ELF core file. */
+ fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC);
+ ASSERT_GE(fd_core_file, 0);
+ ASSERT_TRUE(is_elf_core(fd_core_file));
+ EXPECT_EQ(close(fd_core_file), 0);
}
/* COREDUMP_RECORDS applies to a coredump the kernel writes, nothing else. */
@@ -2320,4 +1942,674 @@ TEST_F(coredump, socket_request_stream_choice_large)
ASSERT_LT(choice.received, choice.size / 8);
}
+/* What a coredump server was built with. */
+struct server_build {
+ /* sizeof(struct coredump_req) and sizeof(struct coredump_ack) back then. */
+ size_t req_size;
+ size_t ack_size;
+ /* The features it raises if the kernel offers them. */
+ __u64 wants;
+ /* Its policy: what it drops from and adds to the task's selection. */
+ __u64 drop;
+ __u64 add;
+};
+
+/* A server from when the structs were first published: kernel-written dumps. */
+static const struct server_build server_build_ver0 = {
+ .req_size = COREDUMP_REQ_SIZE_VER0,
+ .ack_size = COREDUMP_ACK_SIZE_VER0,
+ .wants = COREDUMP_KERNEL,
+};
+
+/* A server built against this header: no shared memory, always the ELF headers. */
+static const struct server_build server_build_ver1 = {
+ .req_size = sizeof(struct coredump_req),
+ .ack_size = sizeof(struct coredump_ack),
+ .wants = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .drop = COREDUMP_MEMORY_ANON_SHARED | COREDUMP_MEMORY_FILE_SHARED,
+ .add = COREDUMP_MEMORY_ELF_HEADERS,
+};
+
+/*
+ * Build the ack the way a server does: from what the kernel offers, what
+ * this build implements, and what fits in the ack the kernel accepts.
+ * Fields the build never read are zero and never consulted.
+ */
+static void negotiate(const struct coredump_req *req,
+ const struct server_build *build,
+ struct coredump_ack *ack)
+{
+ __u64 offered = req->mask & build->wants;
+
+ memset(ack, 0, sizeof(*ack));
+ ack->size = build->ack_size < req->size_ack ? build->ack_size : req->size_ack;
+ /* These builds only ever have the kernel write the coredump. */
+ ack->mask = COREDUMP_KERNEL;
+
+ /* Sparse needs records, records need the kernel to write. */
+ if (offered & COREDUMP_RECORDS) {
+ ack->mask |= COREDUMP_RECORDS;
+ if (offered & COREDUMP_SPARSE)
+ ack->mask |= COREDUMP_SPARSE;
+ }
+
+ /* The memory types need an ack that carries them. */
+ if ((offered & COREDUMP_MEMORY_TYPES) && ack->size >= COREDUMP_ACK_SIZE_VER1) {
+ ack->mask |= COREDUMP_MEMORY_TYPES;
+ /* Start from the task's selection; only advertised types pass. */
+ ack->memory_types = (req->memory_types & ~build->drop) | build->add;
+ ack->memory_types &= req->memory_types_mask;
+ }
+}
+
+/* What a memory types test asks of the kernel and what it expects back. */
+struct memory_choice {
+ /* Memory types the crashing child selects, or FILTER_TASK_INHERIT. */
+ __u64 task_filter;
+ /* Negotiate the ack as this server build, NULL to send it as given. */
+ const struct server_build *build;
+ /* The ack, or what the negotiation must arrive at. */
+ __u64 mask;
+ __u64 memory_types;
+ size_t size_ack;
+ /* The shared mapping is in the coredump with all of its memory. */
+ bool shared_dumped;
+ /* No memory at all. Pull a page from /proc/<pid>/mem instead. */
+ bool skeleton;
+};
+
+/* A skeleton still carries the vdso and friends, nothing bigger. */
+#define SKELETON_DATA_PAGES 16
+
+/*
+ * The crashing child maps shared anonymous memory and tells the server
+ * where. The server acks with @choice and checks whether that mapping's
+ * segment in the coredump carries its memory.
+ */
+static void check_memory_dump(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(coredump) *self,
+ const struct memory_choice *choice)
+{
+ int pidfd, status;
+ pid_t pid, pid_coredump_server;
+ struct pidfd_info info = {};
+ int ipc_sockets[2];
+ int addr_pipe[2];
+ char c;
+
+ ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
+ ASSERT_EQ(pipe(addr_pipe), 0);
+ ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
+
+ pid_coredump_server = fork();
+ ASSERT_GE(pid_coredump_server, 0);
+ if (pid_coredump_server == 0) {
+ int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
+ int fd_file = -1;
+ int exit_code = EXIT_FAILURE;
+ struct coredump_req req = {};
+ struct coredump_ack ack = {
+ .size = choice->size_ack,
+ .mask = choice->mask,
+ .memory_types = choice->memory_types,
+ };
+ /* How much of the request this server reads. */
+ size_t req_size = choice->build ? choice->build->req_size : sizeof(req);
+ __u64 task_filter;
+ ElfW(Phdr) segment;
+ ssize_t received;
+ off_t size;
+ char *addr;
+
+ close(ipc_sockets[0]);
+ close(addr_pipe[1]);
+
+ fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
+ if (fd_server < 0)
+ goto out;
+
+ if (write_nointr(ipc_sockets[1], "1", 1) < 0)
+ goto out;
+
+ close(ipc_sockets[1]);
+
+ fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
+ if (fd_coredump < 0)
+ goto out;
+
+ fd_peer_pidfd = get_peer_pidfd(fd_coredump);
+ if (fd_peer_pidfd < 0)
+ goto out;
+
+ fd_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
+ if (fd_file < 0)
+ goto out;
+
+ if (!read_coredump_req_sized(fd_coredump, &req, req_size))
+ goto out;
+
+ if (!peer_coredump_filter(fd_peer_pidfd, &task_filter))
+ goto out;
+
+ /* A build from before the memory types never read that far. */
+ if (req_size >= COREDUMP_REQ_SIZE_VER1) {
+ if (!check_coredump_req(&req))
+ goto out;
+
+ /* The request reports the memory types the task selected. */
+ if (req.memory_types != task_filter) {
+ fprintf(stderr, "Request reports 0x%llx, task selected 0x%llx\n",
+ (unsigned long long)req.memory_types,
+ (unsigned long long)task_filter);
+ goto out;
+ }
+ }
+
+ if (choice->task_filter != FILTER_TASK_INHERIT &&
+ task_filter != choice->task_filter) {
+ fprintf(stderr, "Task selected 0x%llx, child asked for 0x%llx\n",
+ (unsigned long long)task_filter,
+ (unsigned long long)choice->task_filter);
+ goto out;
+ }
+
+ /* The child sent the address of its mapping before it crashed. */
+ if (read_nointr(addr_pipe[0], &addr, sizeof(addr)) != sizeof(addr))
+ goto out;
+
+ /* A server build negotiates its ack and must arrive at the choice. */
+ if (choice->build) {
+ negotiate(&req, choice->build, &ack);
+
+ if (ack.size != choice->size_ack || ack.mask != choice->mask ||
+ ack.memory_types != choice->memory_types) {
+ fprintf(stderr,
+ "Negotiated %u bytes, mask 0x%llx, types 0x%llx\n",
+ ack.size, (unsigned long long)ack.mask,
+ (unsigned long long)ack.memory_types);
+ goto out;
+ }
+ }
+
+ if (!send_coredump_ack_types(fd_coredump, &req, ack.mask,
+ ack.memory_types, ack.size))
+ goto out;
+
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK))
+ goto out;
+
+ if (ack.mask & COREDUMP_RECORDS)
+ received = recv_coredump_records(fd_coredump, fd_file,
+ &size, NULL, -1);
+ else
+ received = recv_coredump_bytes(fd_coredump, fd_file);
+ if (received < 0)
+ goto out;
+
+ if (!is_elf_core(fd_file))
+ goto out;
+
+ /* A dump ending in holes or empty segments must still be whole. */
+ if (!check_coredump_extent(fd_file))
+ goto out;
+
+ if (!find_coredump_segment(fd_file, (__u64)(uintptr_t)addr, &segment))
+ goto out;
+
+ if (segment.p_memsz != MEMORY_MAPPING_SIZE) {
+ fprintf(stderr, "Segment spans %llu bytes, the mapping %u\n",
+ (unsigned long long)segment.p_memsz,
+ MEMORY_MAPPING_SIZE);
+ goto out;
+ }
+
+ if (segment.p_filesz != (choice->shared_dumped ? segment.p_memsz : 0)) {
+ fprintf(stderr, "Segment carries %llu bytes, expected %s of them\n",
+ (unsigned long long)segment.p_filesz,
+ choice->shared_dumped ? "all" : "none");
+ goto out;
+ }
+
+ if (choice->skeleton) {
+ __u64 data, notes, data_max;
+ char buf[PAGE_SIZE];
+
+ if (!sum_coredump_segments(fd_file, &data, ¬es))
+ goto out;
+
+ data_max = SKELETON_DATA_PAGES * sysconf(_SC_PAGESIZE);
+ if (!notes || data > data_max) {
+ fprintf(stderr, "Skeleton has %llu note and %llu memory bytes\n",
+ (unsigned long long)notes,
+ (unsigned long long)data);
+ goto out;
+ }
+
+ /* The task is parked in COREDUMP_WAIT with its memory. */
+ if (peer_read_mem(fd_peer_pidfd, (__u64)(uintptr_t)addr,
+ buf, sizeof(buf)) != sizeof(buf))
+ goto out;
+
+ if (buf[0] != 'x') {
+ fprintf(stderr, "Pulled memory lacks the child's mark\n");
+ goto out;
+ }
+
+ fprintf(stderr, "Skeleton of %zd bytes, pulled %zu bytes of memory\n",
+ received, sizeof(buf));
+ }
+
+ exit_code = EXIT_SUCCESS;
+out:
+ close(addr_pipe[0]);
+ if (fd_file >= 0)
+ close(fd_file);
+ if (fd_peer_pidfd >= 0)
+ close(fd_peer_pidfd);
+ if (fd_coredump >= 0)
+ close(fd_coredump);
+ if (fd_server >= 0)
+ close(fd_server);
+ _exit(exit_code);
+ }
+ self->pid_coredump_server = pid_coredump_server;
+
+ EXPECT_EQ(close(ipc_sockets[1]), 0);
+ EXPECT_EQ(close(addr_pipe[0]), 0);
+ ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
+ EXPECT_EQ(close(ipc_sockets[0]), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+ if (pid == 0)
+ crashing_child_memory(choice->task_filter, addr_pipe[1]);
+ EXPECT_EQ(close(addr_pipe[1]), 0);
+
+ pidfd = sys_pidfd_open(pid, 0);
+ ASSERT_GE(pidfd, 0);
+
+ waitpid(pid, &status, 0);
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_TRUE(WCOREDUMP(status));
+
+ ASSERT_TRUE(get_pidfd_info(pidfd, &info));
+ ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
+ ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
+
+ wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
+}
+
+/* Without COREDUMP_MEMORY_TYPES the task's own selection decides. */
+TEST_F(coredump, socket_request_memory_types_task_includes)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .mask = COREDUMP_KERNEL,
+ .shared_dumped = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+TEST_F(coredump, socket_request_memory_types_task_excludes)
+{
+ struct memory_choice choice = {
+ .task_filter = 0,
+ .mask = COREDUMP_KERNEL,
+ .shared_dumped = false,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* The server drops a memory type the task would have dumped. */
+TEST_F(coredump, socket_request_memory_types_restricts)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE,
+ .shared_dumped = false,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* The server adds a memory type the task had excluded. */
+TEST_F(coredump, socket_request_memory_types_widens)
+{
+ struct memory_choice choice = {
+ .task_filter = 0,
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .shared_dumped = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* The memory types decide what goes into a record stream just the same. */
+TEST_F(coredump, socket_request_memory_types_records)
+{
+ struct memory_choice choice = {
+ .task_filter = FILTER_TASK_INHERIT,
+ .mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE,
+ .shared_dumped = false,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/*
+ * An empty selection leaves a skeleton: every program header and every note
+ * but no memory. A server that wants to pick the memory itself reads it
+ * from /proc/<pid>/mem while the task waits for it to finish.
+ */
+TEST_F(coredump, socket_request_memory_types_skeleton)
+{
+ struct memory_choice choice = {
+ .task_filter = FILTER_TASK_INHERIT,
+ .mask = COREDUMP_KERNEL | COREDUMP_WAIT | COREDUMP_MEMORY_TYPES,
+ .memory_types = 0,
+ .shared_dumped = false,
+ .skeleton = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* A memory type the kernel didn't advertise in memory_types_mask. */
+TEST_F(coredump, socket_request_memory_types_unknown_bit)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ .memory_types = 1ULL << 63,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_UNSUPPORTED,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* The memory types must be zero unless COREDUMP_MEMORY_TYPES is raised. */
+TEST_F(coredump, socket_request_memory_types_stale_field)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = COREDUMP_KERNEL,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_UNSUPPORTED,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* COREDUMP_MEMORY_TYPES needs an ack that has the memory types. */
+TEST_F(coredump, socket_request_memory_types_short_ack)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = COREDUMP_ACK_SIZE_VER0,
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ },
+ .bytes = COREDUMP_ACK_SIZE_VER0,
+ .mark = COREDUMP_MARK_MINSIZE,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* The memory types select what the kernel writes, nothing else. */
+TEST_F(coredump, socket_request_memory_types_without_kernel)
+{
+ check_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_MEMORY_TYPES);
+}
+
+/*
+ * A server built with the first structs reads the request it knows,
+ * discards the rest and acks with the ack it knows. It raises nothing
+ * it wasn't built for and the kernel dumps what the task selected.
+ */
+TEST_F(coredump, socket_request_negotiate_ver0)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .build = &server_build_ver0,
+ .mask = COREDUMP_KERNEL,
+ .memory_types = 0,
+ .size_ack = COREDUMP_ACK_SIZE_VER0,
+ .shared_dumped = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/*
+ * A server built against this header takes every feature the kernel
+ * offers, drops shared memory from what the task selected and adds the
+ * ELF headers.
+ */
+TEST_F(coredump, socket_request_negotiate_ver1)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .build = &server_build_ver1,
+ .mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ELF_HEADERS,
+ .size_ack = COREDUMP_ACK_SIZE_VER1,
+ .shared_dumped = false,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* An ack that picks none of KERNEL, USERSPACE and REJECT. */
+TEST_F(coredump, socket_request_no_mode)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = COREDUMP_WAIT,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_CONFLICTING,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* @spare must be zero, like every field that isn't in use. */
+TEST_F(coredump, socket_request_spare)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .spare = 1,
+ .mask = COREDUMP_KERNEL,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_UNSUPPORTED,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* An ack size is a byte count. One that ends inside a field is valid. */
+#define ACK_SIZE_BETWEEN (COREDUMP_ACK_SIZE_VER0 + sizeof(__u32))
+
+/* Any size from VER0 up to what the kernel accepts works without memory types. */
+TEST_F(coredump, socket_request_ack_size_between)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .mask = COREDUMP_KERNEL,
+ .size_ack = ACK_SIZE_BETWEEN,
+ .shared_dumped = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* The memory types need the whole field, not the part that happens to fit. */
+TEST_F(coredump, socket_request_memory_types_ack_size_between)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = ACK_SIZE_BETWEEN,
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ },
+ .bytes = ACK_SIZE_BETWEEN,
+ .mark = COREDUMP_MARK_MINSIZE,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* A server that hangs up without acking gets no marker and no coredump. */
+TEST_F(coredump, socket_request_server_hangs_up)
+{
+ struct refused_ack refused = {
+ .bytes = 0,
+ .no_marker = true,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* A server that hangs up in the middle of its ack looks the same. */
+TEST_F(coredump, socket_request_ack_truncated)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = COREDUMP_ACK_SIZE_VER0,
+ .mask = COREDUMP_KERNEL,
+ },
+ .bytes = COREDUMP_ACK_SIZE_VER0 / 2,
+ .no_marker = true,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/*
+ * The kernels a server built against this header can't meet here:
+ * negotiate() against their requests, no coredump involved.
+ */
+
+/* The request of a kernel with the first structs and features. */
+static const struct coredump_req req_ver0 = {
+ .size = COREDUMP_REQ_SIZE_VER0,
+ .size_ack = COREDUMP_ACK_SIZE_VER0,
+ .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
+ COREDUMP_REJECT | COREDUMP_WAIT,
+};
+
+/* The request of this kernel. */
+static const struct coredump_req req_ver1 = {
+ .size = COREDUMP_REQ_SIZE_VER1,
+ .size_ack = COREDUMP_ACK_SIZE_VER1,
+ .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
+ COREDUMP_REJECT | COREDUMP_WAIT |
+ COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .memory_types_mask = TEST_MEMORY_ALL,
+};
+
+/* A kernel with the first structs gets the first ack and nothing newer. */
+TEST(negotiate_ver0_kernel)
+{
+ struct coredump_ack ack;
+
+ negotiate(&req_ver0, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL);
+ ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* A kernel with records and sparse but the first structs: both, no types. */
+TEST(negotiate_sparse_kernel)
+{
+ struct coredump_req req = req_ver0;
+ struct coredump_ack ack;
+
+ req.mask |= COREDUMP_RECORDS | COREDUMP_SPARSE;
+ negotiate(&req, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE);
+ ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* Records without sparse: sparse isn't raised on its own. */
+TEST(negotiate_records_without_sparse)
+{
+ struct coredump_req req = req_ver0;
+ struct coredump_ack ack;
+
+ req.mask |= COREDUMP_RECORDS;
+ negotiate(&req, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS);
+}
+
+/*
+ * A feature whose ack field lies past what the kernel accepts can't be
+ * raised. No kernel offers the memory types without the room for them, so a
+ * request that does stands in for a feature newer than this header.
+ */
+TEST(negotiate_types_need_room)
+{
+ struct coredump_req req = req_ver0;
+ struct coredump_ack ack;
+
+ req.mask |= COREDUMP_MEMORY_TYPES;
+ negotiate(&req, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL);
+ ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* This kernel: the policy applied to the task's selection. */
+TEST(negotiate_ver1_kernel)
+{
+ struct coredump_ack ack;
+
+ negotiate(&req_ver1, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER1);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES);
+ ASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ELF_HEADERS);
+}
+
+/* A kernel that doesn't know a type the policy adds isn't asked for it. */
+TEST(negotiate_unknown_type)
+{
+ struct coredump_req req = req_ver1;
+ struct coredump_ack ack;
+
+ req.memory_types_mask &= ~(__u64)COREDUMP_MEMORY_ELF_HEADERS;
+ negotiate(&req, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES);
+ ASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE);
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c
index d7cc448eeaf44..4e36e3e4fb78f 100644
--- a/tools/testing/selftests/coredump/coredump_test_helpers.c
+++ b/tools/testing/selftests/coredump/coredump_test_helpers.c
@@ -17,6 +17,7 @@
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
@@ -73,6 +74,48 @@ void crashing_child_sparse(size_t size)
*(volatile int *)NULL = 0;
}
+/* Select @types through the caller's own /proc/self/coredump_filter. */
+static bool set_coredump_filter(__u64 types)
+{
+ char buf[32];
+ int fd, len;
+ bool ok;
+
+ fd = open("/proc/self/coredump_filter", O_WRONLY | O_CLOEXEC);
+ if (fd < 0)
+ return false;
+
+ len = snprintf(buf, sizeof(buf), "0x%llx", (unsigned long long)types);
+ ok = write_nointr(fd, buf, len) == len;
+ close(fd);
+ return ok;
+}
+
+/*
+ * Map shared anonymous memory, touch it, tell the server where it is and
+ * crash. A @task_filter other than FILTER_TASK_INHERIT is selected first.
+ */
+void crashing_child_memory(__u64 task_filter, int fd_addr)
+{
+ char *p;
+
+ if (task_filter != FILTER_TASK_INHERIT && !set_coredump_filter(task_filter))
+ _exit(EXIT_FAILURE);
+
+ p = mmap(NULL, MEMORY_MAPPING_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (p == MAP_FAILED)
+ _exit(EXIT_FAILURE);
+ p[0] = 'x';
+
+ if (write_nointr(fd_addr, &p, sizeof(p)) != sizeof(p))
+ _exit(EXIT_FAILURE);
+ close(fd_addr);
+
+ /* crash on purpose */
+ *(volatile int *)NULL = 0;
+}
+
/* Sink a reassembled record stream is handed to, record by record. */
struct coredump_record_sink {
/* @len bytes of coredump data that belong at @offset. */
@@ -916,6 +959,82 @@ static const ElfW(Phdr) *find_segment(const ElfW(Phdr) *phdr, size_t nr,
return NULL;
}
+/* The PT_LOAD segment @vaddr falls into. */
+bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment)
+{
+ const ElfW(Phdr) *found;
+ ElfW(Phdr) *phdr;
+ size_t nr;
+
+ phdr = read_phdrs(fd, &nr);
+ if (!phdr)
+ return false;
+
+ found = find_segment(phdr, nr, vaddr);
+ if (found)
+ *segment = *found;
+ else
+ fprintf(stderr, "%s: no segment for 0x%llx\n", __func__,
+ (unsigned long long)vaddr);
+
+ free(phdr);
+ return found;
+}
+
+/* How many bytes the PT_LOAD and the PT_NOTE segments of @fd carry. */
+bool sum_coredump_segments(int fd, __u64 *data, __u64 *notes)
+{
+ ElfW(Phdr) *phdr;
+ size_t nr, i;
+
+ phdr = read_phdrs(fd, &nr);
+ if (!phdr)
+ return false;
+
+ *data = 0;
+ *notes = 0;
+ for (i = 0; i < nr; i++) {
+ if (phdr[i].p_type == PT_LOAD)
+ *data += phdr[i].p_filesz;
+ else if (phdr[i].p_type == PT_NOTE)
+ *notes += phdr[i].p_filesz;
+ }
+
+ free(phdr);
+ return true;
+}
+
+/* The coredump in @fd is at least as long as every segment it declares. */
+bool check_coredump_extent(int fd)
+{
+ ElfW(Phdr) *phdr;
+ struct stat st;
+ size_t nr, i;
+ bool ok = true;
+
+ if (fstat(fd, &st)) {
+ fprintf(stderr, "%s: fstat: %m\n", __func__);
+ return false;
+ }
+
+ phdr = read_phdrs(fd, &nr);
+ if (!phdr)
+ return false;
+
+ for (i = 0; i < nr; i++) {
+ if (phdr[i].p_offset + phdr[i].p_filesz <= (__u64)st.st_size)
+ continue;
+ fprintf(stderr, "%s: segment %zu ends at %llu, the coredump at %llu\n",
+ __func__, i,
+ (unsigned long long)(phdr[i].p_offset + phdr[i].p_filesz),
+ (unsigned long long)st.st_size);
+ ok = false;
+ }
+
+ free(phdr);
+ return ok;
+}
+
/* The next stretch of memory the segments cover, split ones merged back. */
static bool next_range(const ElfW(Phdr) *phdr, size_t nr, size_t *i,
__u64 *start, __u64 *end)
@@ -1250,6 +1369,62 @@ ssize_t peer_vm_size(int fd_peer_pidfd)
/* Protocol helper functions */
+/* The peer's /proc/<pid>/coredump_filter, which is in memory types. */
+bool peer_coredump_filter(int fd_peer_pidfd, __u64 *memory_types)
+{
+ struct pidfd_info info = {};
+ unsigned long value;
+ char path[64];
+ FILE *f;
+ int ret;
+
+ if (!get_pidfd_info(fd_peer_pidfd, &info))
+ return false;
+
+ snprintf(path, sizeof(path), "/proc/%d/coredump_filter", info.pid);
+ f = fopen(path, "r");
+ if (!f) {
+ fprintf(stderr, "%s: %s: %m\n", __func__, path);
+ return false;
+ }
+
+ ret = fscanf(f, "%lx", &value);
+ fclose(f);
+ if (ret != 1) {
+ fprintf(stderr, "%s: %s: no value\n", __func__, path);
+ return false;
+ }
+
+ *memory_types = value;
+ return true;
+}
+
+/* Read @len bytes at @addr from the peer's /proc/<pid>/mem. */
+ssize_t peer_read_mem(int fd_peer_pidfd, __u64 addr, void *buf, size_t len)
+{
+ struct pidfd_info info = {};
+ char path[64];
+ ssize_t ret;
+ int fd;
+
+ if (!get_pidfd_info(fd_peer_pidfd, &info))
+ return -1;
+
+ snprintf(path, sizeof(path), "/proc/%d/mem", info.pid);
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ fprintf(stderr, "%s: %s: %m\n", __func__, path);
+ return -1;
+ }
+
+ ret = pread(fd, buf, len, addr);
+ if (ret < 0)
+ fprintf(stderr, "%s: %s at 0x%llx: %m\n", __func__, path,
+ (unsigned long long)addr);
+ close(fd);
+ return ret;
+}
+
ssize_t recv_marker(int fd)
{
enum coredump_mark mark = COREDUMP_MARK_REQACK;
@@ -1292,10 +1467,34 @@ bool read_marker(int fd, enum coredump_mark mark)
return ret == mark;
}
-bool read_coredump_req(int fd, struct coredump_req *req)
+/*
+ * The kernel hung up without sending anything more: end of stream, or a
+ * reset if it refused the ack on its peeked size and never read it.
+ */
+bool read_hangup(int fd)
{
ssize_t ret;
- size_t field_size, user_size, known_size, kernel_size, remaining_size;
+ char c;
+
+ ret = recv(fd, &c, sizeof(c), MSG_WAITALL);
+ if (ret == 0) {
+ fprintf(stderr, "Kernel closed the connection\n");
+ return true;
+ }
+ if (ret < 0 && errno == ECONNRESET) {
+ fprintf(stderr, "Kernel closed the connection with the ack unread\n");
+ return true;
+ }
+
+ fprintf(stderr, "%s: expected a hangup, got %zd: %m\n", __func__, ret);
+ return false;
+}
+
+/* Read the request as a server built with a @user_size byte struct does. */
+bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size)
+{
+ ssize_t ret;
+ size_t field_size, known_size, kernel_size, remaining_size;
memset(req, 0, sizeof(*req));
field_size = sizeof(req->size);
@@ -1303,25 +1502,24 @@ bool read_coredump_req(int fd, struct coredump_req *req)
/* Peek the size of the coredump request. */
ret = recv(fd, req, field_size, MSG_PEEK | MSG_WAITALL);
if (ret != field_size) {
- fprintf(stderr, "read_coredump_req: peek failed (got %zd, expected %zu): %m\n",
+ fprintf(stderr, "%s: peek failed (got %zd, expected %zu): %m\n", __func__,
ret, field_size);
return false;
}
kernel_size = req->size;
if (kernel_size < COREDUMP_REQ_SIZE_VER0) {
- fprintf(stderr, "read_coredump_req: kernel_size %zu < min %d\n",
+ fprintf(stderr, "%s: kernel_size %zu < min %d\n", __func__,
kernel_size, COREDUMP_REQ_SIZE_VER0);
return false;
}
if (kernel_size >= PAGE_SIZE) {
- fprintf(stderr, "read_coredump_req: kernel_size %zu >= PAGE_SIZE %d\n",
+ fprintf(stderr, "%s: kernel_size %zu >= PAGE_SIZE %d\n", __func__,
kernel_size, PAGE_SIZE);
return false;
}
/* Consume as much of the request as we know about. */
- user_size = sizeof(struct coredump_req);
known_size = user_size < kernel_size ? user_size : kernel_size;
ret = recv(fd, req, known_size, MSG_WAITALL);
if (ret != known_size)
@@ -1354,8 +1552,13 @@ bool read_coredump_req(int fd, struct coredump_req *req)
return true;
}
-bool send_coredump_ack(int fd, const struct coredump_req *req,
- __u64 mask, size_t size_ack)
+bool read_coredump_req(int fd, struct coredump_req *req)
+{
+ return read_coredump_req_sized(fd, req, sizeof(*req));
+}
+
+/* Send @len bytes of @ack as they are, more than the struct if asked to. */
+bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len)
{
ssize_t ret;
/*
@@ -1367,34 +1570,60 @@ bool send_coredump_ack(int fd, const struct coredump_req *req,
char buffer[PAGE_SIZE];
} large_ack = {};
- if (!size_ack)
- size_ack = sizeof(struct coredump_ack) < req->size_ack ?
- sizeof(struct coredump_ack) :
- req->size_ack;
- large_ack.ack.mask = mask;
- large_ack.ack.size = size_ack;
- ret = send(fd, &large_ack, size_ack, MSG_NOSIGNAL);
- if (ret != size_ack) {
+ if (len > sizeof(large_ack))
+ return false;
+
+ large_ack.ack = *ack;
+ ret = send(fd, &large_ack, len, MSG_NOSIGNAL);
+ if (ret != len) {
fprintf(stderr, "%s: short send %zd: %m\n", __func__, ret);
return false;
}
- fprintf(stderr, "Sent coredump ack with size %zu and mask 0x%llx\n",
- size_ack, (unsigned long long)mask);
+ fprintf(stderr, "Sent %zu bytes of coredump ack: size %u, mask 0x%llx, types 0x%llx\n",
+ len, ack->size, (unsigned long long)ack->mask,
+ (unsigned long long)ack->memory_types);
return true;
}
+bool send_coredump_ack_types(int fd, const struct coredump_req *req,
+ __u64 mask, __u64 memory_types, size_t size_ack)
+{
+ struct coredump_ack ack = {
+ .mask = mask,
+ .memory_types = memory_types,
+ };
+
+ if (!size_ack)
+ size_ack = sizeof(struct coredump_ack) < req->size_ack ?
+ sizeof(struct coredump_ack) :
+ req->size_ack;
+ ack.size = size_ack;
+ return send_coredump_ack_bytes(fd, &ack, size_ack);
+}
+
+bool send_coredump_ack(int fd, const struct coredump_req *req,
+ __u64 mask, size_t size_ack)
+{
+ return send_coredump_ack_types(fd, req, mask, 0, size_ack);
+}
+
/* Every option the kernel is expected to advertise in coredump_req->mask. */
#define TEST_REQ_MASK_ALL \
(COREDUMP_KERNEL | COREDUMP_USERSPACE | \
COREDUMP_REJECT | COREDUMP_WAIT | \
- COREDUMP_RECORDS | COREDUMP_SPARSE)
+ COREDUMP_RECORDS | COREDUMP_SPARSE | COREDUMP_MEMORY_TYPES)
bool check_coredump_req(const struct coredump_req *req)
{
- if (req->size < COREDUMP_REQ_SIZE_VER0) {
- fprintf(stderr, "%s: size %u below minimum %d\n",
- __func__, req->size, COREDUMP_REQ_SIZE_VER0);
+ if (req->size != COREDUMP_REQ_SIZE_VER1) {
+ fprintf(stderr, "%s: size %u, expected %d\n",
+ __func__, req->size, COREDUMP_REQ_SIZE_VER1);
+ return false;
+ }
+ if (req->size_ack != COREDUMP_ACK_SIZE_VER1) {
+ fprintf(stderr, "%s: size_ack %u, expected %d\n",
+ __func__, req->size_ack, COREDUMP_ACK_SIZE_VER1);
return false;
}
if (req->mask != TEST_REQ_MASK_ALL) {
@@ -1403,6 +1632,12 @@ bool check_coredump_req(const struct coredump_req *req)
(unsigned long long)TEST_REQ_MASK_ALL);
return false;
}
+ if (req->memory_types_mask != TEST_MEMORY_ALL) {
+ fprintf(stderr, "%s: memory_types_mask 0x%llx, expected 0x%llx\n",
+ __func__, (unsigned long long)req->memory_types_mask,
+ (unsigned long long)TEST_MEMORY_ALL);
+ return false;
+ }
return true;
}
diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h
index 97ad5cfeae928..3f2f87837558e 100644
--- a/tools/testing/selftests/coredump/coredump_test_helpers.h
+++ b/tools/testing/selftests/coredump/coredump_test_helpers.h
@@ -3,6 +3,7 @@
#ifndef __COREDUMP_TEST_HELPERS_H
#define __COREDUMP_TEST_HELPERS_H
+#include <link.h>
#include <stdbool.h>
#include <sys/types.h>
#include <linux/coredump.h>
@@ -21,10 +22,30 @@
/* A task mapping at least this much is worth a record stream. */
#define SPARSE_STREAM_THRESHOLD (SPARSE_MAPPING_SIZE / 2)
+/* Size of the shared anonymous mapping the memory types tests map. */
+#define MEMORY_MAPPING_SIZE (4 * 1024 * 1024)
+
+/* Leave the coredump_filter the crashing child inherited alone. */
+#define FILTER_TASK_INHERIT ((__u64)-1)
+
+/* Every memory type the kernel is expected to advertise. */
+#define TEST_MEMORY_ALL \
+ (COREDUMP_MEMORY_ANON_PRIVATE | COREDUMP_MEMORY_ANON_SHARED | \
+ COREDUMP_MEMORY_FILE_PRIVATE | COREDUMP_MEMORY_FILE_SHARED | \
+ COREDUMP_MEMORY_ELF_HEADERS | \
+ COREDUMP_MEMORY_HUGETLB_PRIVATE | COREDUMP_MEMORY_HUGETLB_SHARED | \
+ COREDUMP_MEMORY_DAX_PRIVATE | COREDUMP_MEMORY_DAX_SHARED)
+
/* Shared helper function declarations */
void *do_nothing(void *arg);
void crashing_child(void);
void crashing_child_sparse(size_t size);
+void crashing_child_memory(__u64 task_filter, int fd_addr);
+bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment);
+bool sum_coredump_segments(int fd, __u64 *data, __u64 *notes);
+bool check_coredump_extent(int fd);
+bool peer_coredump_filter(int fd_peer_pidfd, __u64 *memory_types);
+ssize_t peer_read_mem(int fd_peer_pidfd, __u64 addr, void *buf, size_t len);
ssize_t recv_coredump_records(int fd_coredump, int fd_core_file,
off_t *coredump_size, bool *truncated,
int fd_peer_pidfd);
@@ -43,9 +64,14 @@ bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);
/* Protocol helper function declarations */
ssize_t recv_marker(int fd);
bool read_marker(int fd, enum coredump_mark mark);
+bool read_hangup(int fd);
bool read_coredump_req(int fd, struct coredump_req *req);
+bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size);
bool send_coredump_ack(int fd, const struct coredump_req *req,
__u64 mask, size_t size_ack);
+bool send_coredump_ack_types(int fd, const struct coredump_req *req,
+ __u64 mask, __u64 memory_types, size_t size_ack);
+bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len);
bool check_coredump_req(const struct coredump_req *req);
int open_coredump_tmpfile(int fd_tmpfs_detached);
void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file);
|
| 3/2 | 2026/08/21 13:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 38781 output: 18 thoughts: 387 Thoughts: **My Analysis of the Coredump Patch Series** Okay, so I'm looking at this patch series that's tweaking the coredump socket protocol. The goal seems to be making the coredump server a bit more dynamic in what memory types it includes in the core dump. Smart. It looks like the core modifications are in `fs/coredump.c`, with supporting changes in the header files and some tests over in `tools/testing/selftests/coredump/`. Specifically, in `fs/coredump.c`, I see they've added `COREDUMP_MEMORY_TYPES` to the protocol. They're updating the `coredump_sock_request` function to handle `memory_types` and `memory_types_mask` within the `struct coredump_req`. I also see that they're processing `ack.memory_types` if the corresponding mask bit is set, which is going to allow the server to tell the client what types to dump. Interesting. And then, crucially, `vma_dump_size` is being changed to use the `memory_types` variable instead of the `mm_flags`. That's where the actual change in behavior is. From a functional perspective, this is a significant change in core kernel coredump logic, making it a prime candidate for fuzzing. We really want to make sure this is robust. Now, let's nail down the key functions to target for more focused analysis and fuzzing. `coredump_sock_request` is clearly important, that's the entry point. `vma_dump_size` is central to the data selection logic. And then the bigger picture, of course, the `vfs_coredump` and `do_coredump` functions are what we're ultimately interested in. Finally, I should check the kernel configs to make sure there aren't any prerequisites. Coredump logic is usually enabled by default, but let's just make sure. I'll need to check `fs/Kconfig` or even `fs/coredump.c` to see if there are any specific options. Ah, looks like `CONFIG_COREDUMP` is the main one. And just to be absolutely sure, I'll use `grepper` to search for `COREDUMP` in the `Kconfig` files. Just to confirm my assumptions and ensure there's no subtle conditional dependency. |
| 4/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "config COREDUMP"
}Results: {
"Output": "fs/Kconfig.binfmt=145=config BINFMT_MISC\n--\nfs/Kconfig.binfmt-170-\nfs/Kconfig.binfmt:171:config COREDUMP\nfs/Kconfig.binfmt-172-\tbool \"Enable core dump support\" if EXPERT\n"
} |
| 5/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 39791 output: 30 thoughts: 29 Thoughts: Here's my take on the thought process, as if it were my own: **Investigating Core Dump Configuration** Okay, so `CONFIG_COREDUMP` is the key configuration variable we're working with here. My instinct is to quickly check the associated source code to see if there are any related configurations lurking within the kernel code. Specifically, I'm thinking about `fs/coredump.c` – that's the likely location for core dump functionality, and it's a good bet to contain dependent settings. Let's see if we can find any relevant macros or flags defined there that might affect how core dumps are handled, their format, or their behavior. A quick scan should tell the tale. |
| 6/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "#ifdef CONFIG_",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=665=static_assert(sizeof(struct coredump_record_header) == COREDUMP_RECORD_HEADER_SIZE_VER0);\nfs/coredump.c-666-\nfs/coredump.c:667:#ifdef CONFIG_UNIX\nfs/coredump.c-668-/* af_unix halves the send buffer to size a single skb. */\n--\nfs/coredump.c=1499=EXPORT_SYMBOL(dump_skip);\nfs/coredump.c-1500-\nfs/coredump.c:1501:#ifdef CONFIG_ELF_CORE\nfs/coredump.c-1502-static bool dump_emit_page(struct coredump_params *cprm, struct page *page)\n--\nfs/coredump.c=1633=EXPORT_SYMBOL(dump_align);\nfs/coredump.c-1634-\nfs/coredump.c:1635:#ifdef CONFIG_SYSCTL\nfs/coredump.c-1636-\n--\nfs/coredump.c=1715=static char core_modes[] = {\nfs/coredump.c-1716-\t\"file\\npipe\"\nfs/coredump.c:1717:#ifdef CONFIG_UNIX\nfs/coredump.c-1718-\t\"\\nsocket\"\n"
} |
| 7/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40348 output: 32 thoughts: 35 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Analyzing the Socket Coredump Implementation** Alright, so we're diving into socket coredumps. The very first thing that pops into my head is that `CONFIG_UNIX` is a prerequisite. That's not surprising, given the nature of sockets and how they interact with the kernel's file system, which is what `CONFIG_UNIX` enables. I need to keep that in mind as I start to investigate. My next move? Definitely heading straight for `fs/coredump.c`. Specifically, I'm going to look for `coredump_sock_request`. That function is probably the heart of how this all comes together. It'll be the one that handles requests for socket-related coredumps, and that's where I can expect to find the core logic for the socket coredump process. I'll need to understand what it does with socket data and how it interacts with the coredump infrastructure. Knowing how `coredump_sock_request` operates will give me a solid grasp on the entire flow. |
| 8/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "coredump_sock_request",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=821=static inline void coredump_sock_shutdown(struct file *file)\n--\nfs/coredump.c-832-\nfs/coredump.c:833:static bool coredump_sock_request(struct core_name *cn, struct coredump_params *cprm)\nfs/coredump.c-834-{\n--\nfs/coredump.c=947=static bool coredump_socket(struct core_name *cn, struct coredump_params *cprm)\n--\nfs/coredump.c-951-\nfs/coredump.c:952:\treturn coredump_sock_request(cn, cprm);\nfs/coredump.c-953-}\n"
} |
| 9/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 40852 output: 30 thoughts: 14 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Analyzing `coredump_socket` Usage** Alright, let's dive in. My immediate thought is, "Where exactly *is* this `coredump_socket` function being invoked?" This isn't about just *knowing* that it exists; it's about understanding its operational context. I need to trace its usage to grasp how it fits into the broader system architecture, specifically in the realm of core dump handling. The call sites will be critical: they'll expose the circumstances that trigger core dump generation, the data being passed in, and the specific operations performed on the socket. This will give me a better understanding of potential security implications, performance bottlenecks, or even subtle bugs related to error handling in the event of a crash. Knowing the call locations is the first crucial step in understanding the socket's responsibility and the overall robustness of the core dump mechanism. |
| 10/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "coredump_socket",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=833=static bool coredump_sock_request(struct core_name *cn, struct coredump_params *cprm)\n--\nfs/coredump.c-946-\nfs/coredump.c:947:static bool coredump_socket(struct core_name *cn, struct coredump_params *cprm)\nfs/coredump.c-948-{\n--\nfs/coredump.c=956=static inline void coredump_sock_shutdown(struct file *file) { }\nfs/coredump.c:957:static inline bool coredump_socket(struct core_name *cn, struct coredump_params *cprm) { return false; }\nfs/coredump.c-958-#endif\n--\nfs/coredump.c=1194=static void do_coredump(struct core_name *cn, struct coredump_params *cprm,\n--\nfs/coredump.c-1215-\tcase COREDUMP_SOCK:\nfs/coredump.c:1216:\t\tif (!coredump_socket(cn, cprm))\nfs/coredump.c-1217-\t\t\treturn;\n--\nfs/coredump.c=1637=void validate_coredump_safety(void)\n--\nfs/coredump.c-1647-\nfs/coredump.c:1648:static inline bool check_coredump_socket(void)\nfs/coredump.c-1649-{\n--\nfs/coredump.c=1687=static int proc_dostring_coredump(const struct ctl_table *table, int write,\n--\nfs/coredump.c-1702-\nfs/coredump.c:1703:\tif (!check_coredump_socket()) {\nfs/coredump.c-1704-\t\tstrscpy(core_pattern, old_core_pattern, retval + 1);\n"
} |
| 11/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41588 output: 32 thoughts: 58 Thoughts: Here's my thought process, as it went down: **Investigating Core Dump Functionality Dependencies** Okay, so `do_coredump` is the function that's ultimately calling `coredump_socket`. That's the key connection. Right away, I see that `coredump_socket` is wrapped within an `#ifdef CONFIG_UNIX` preprocessor directive. So, *that* tells me pretty definitively that `CONFIG_UNIX` is a required configuration option for this functionality to even be present, much less work as expected. I need that to be enabled. Now, what's `vma_dump_size` doing in this whole mix? I should probably check that next to see how it might be related. |
| 12/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "vma_dump_size",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=1789=static bool always_dump_vma(struct vm_area_struct *vma)\n--\nfs/coredump.c-1818- */\nfs/coredump.c:1819:static unsigned long vma_dump_size(struct vm_area_struct *vma,\nfs/coredump.c-1820-\t\t\t\t u64 memory_types)\n--\nfs/coredump.c=1947=static bool dump_vma_snapshot(struct coredump_params *cprm)\n--\nfs/coredump.c-1977-\t\tm-\u003eflags = vma-\u003evm_flags;\nfs/coredump.c:1978:\t\tm-\u003edump_size = vma_dump_size(vma, cprm-\u003ememory_types);\nfs/coredump.c-1979-\t\tm-\u003epgoff = vma-\u003evm_pgoff;\n"
} |
| 13/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 42076 output: 20 thoughts: 56 Thoughts: Here's my summary, as though I'm thinking through this process: **Tracing the Core Dump Functionality** Okay, so I'm trying to figure out the call chain for `vma_dump_size`. I remember seeing that it's called by `dump_vma_snapshot`, which suggests that function is likely the immediate caller. My suspicion is that `dump_vma_snapshot` itself gets called by `elf_core_dump`. That seems like the most logical link, given what I know about ELF core dumps and how the kernel handles them. Now I should probably double-check that this is correct, and I bet I can find the call within `fs/binfmt_elf.c`. I'm pretty sure that's where the ELF-specific core dump handling lives. Let's go confirm. |
| 14/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "dump_vma_snapshot"
}Results: {
"Output": "fs/coredump.c-67-\nfs/coredump.c:68:static bool dump_vma_snapshot(struct coredump_params *cprm);\nfs/coredump.c-69-static void free_vma_snapshot(struct coredump_params *cprm);\n--\nfs/coredump.c=1129=static bool coredump_write(struct coredump_params *cprm,\n--\nfs/coredump.c-1136-\nfs/coredump.c:1137:\tif (!dump_vma_snapshot(cprm)) {\nfs/coredump.c-1138-\t\tcprm-\u003estate |= COREDUMP_STATE_TRUNCATED;\n--\nfs/coredump.c=1819=static unsigned long vma_dump_size(struct vm_area_struct *vma,\n--\nfs/coredump.c-1888-\t\t * Use a placeholder instead, and fix it up later in\nfs/coredump.c:1889:\t\t * dump_vma_snapshot().\nfs/coredump.c-1890-\t\t */\n--\nfs/coredump.c=1931=static int cmp_vma_size(const void *vma_meta_lhs_ptr, const void *vma_meta_rhs_ptr)\n--\nfs/coredump.c-1946- */\nfs/coredump.c:1947:static bool dump_vma_snapshot(struct coredump_params *cprm)\nfs/coredump.c-1948-{\n"
} |
| 15/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 42725 output: 35 thoughts: 47 Thoughts: Here's my take on those thoughts: **Analyzing the Coredump Process** Okay, so `dump_vma_snapshot`… I thought that was called directly by `coredump_write`, but let's double-check. I need to be precise here. Let me go straight to the source. Right, `fs/coredump.c` is the obvious place to find the context around that `dump_vma_snapshot` call. I need to understand exactly how the virtual memory area snapshots are being handled during the coredump writing process. This context is key to understanding the overall coredump flow and any potential issues. Gotta see how it all fits together. |
| 16/2 | 2026/08/21 13:50 | tool | read-file |
0mArgs:{
"File": "fs/coredump.c",
"FirstLine": 1129,
"LineCount": 20
}Results: {
"Contents": "1129:\tstatic bool coredump_write(struct coredump_params *cprm,\n1130:\t\t\t\t const struct linux_binfmt *binfmt)\n1131:\t{\n1132:\t\tif (dump_interrupted()) {\n1133:\t\t\tcprm-\u003estate |= COREDUMP_STATE_TRUNCATED;\n1134:\t\t\treturn true;\n1135:\t\t}\n1136:\t\n1137:\t\tif (!dump_vma_snapshot(cprm)) {\n1138:\t\t\tcprm-\u003estate |= COREDUMP_STATE_TRUNCATED;\n1139:\t\t\treturn false;\n1140:\t\t}\n1141:\t\n1142:\t\tfile_start_write(cprm-\u003efile);\n1143:\t\tif (!binfmt-\u003ecore_dump(cprm))\n1144:\t\t\tcprm-\u003estate |= COREDUMP_STATE_TRUNCATED;\n1145:\t\t/*\n1146:\t\t * A trailing hole still has to land in the coredump. Seeking over\n1147:\t\t * it doesn't grow the file, so the last byte of it is written\n1148:\t\t * instead and gdb doesn't see a truncated file. Everything else\n"
} |
| 17/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 43281 output: 30 thoughts: 70 Thoughts: Here's my summary, as though I'm thinking through this process: **Diving into the Kernel Core Dump Flow** Okay, so I'm tracing the core dump path in the kernel. It looks like `coredump_write` is the workhorse here. I see it calls `dump_vma_snapshot` which I'm familiar with – it's responsible for capturing the virtual memory areas, and then `binfmt->core_dump`. That's interesting, the binary format handler's `core_dump` function. So, each executable format potentially has a specific way it handles core dumps. Now, where does `coredump_write` get called? Ah, it's called by `do_coredump`. Bingo. `do_coredump` is the likely entry point. That's where the core dump process initiates. Time to dig into `do_coredump` and see what triggers it and how the whole thing is orchestrated. |
| 18/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "do_coredump",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=1182=static inline bool coredump_skip(const struct coredump_params *cprm,\n--\nfs/coredump.c-1193-\nfs/coredump.c:1194:static void do_coredump(struct core_name *cn, struct coredump_params *cprm,\nfs/coredump.c-1195-\t\t\tsize_t **argv, int *argc, const struct linux_binfmt *binfmt)\n--\nfs/coredump.c=1267=void vfs_coredump(const kernel_siginfo_t *siginfo)\n--\nfs/coredump.c-1305-\tscoped_with_creds(cred)\nfs/coredump.c:1306:\t\tdo_coredump(\u0026cn, \u0026cprm, \u0026argv, \u0026argc, binfmt);\nfs/coredump.c-1307-\tcoredump_cleanup(\u0026cn, \u0026cprm);\n"
} |
| 19/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 43836 output: 19 thoughts: 33 Thoughts: Okay, here's my interpretation of those thoughts, summarized from my perspective as an expert in the field: **Diving into the Core Dump Entry Point** Right, so `vfs_coredump` is where we're starting. That's the function we need to understand to get a handle on this. The first thing I'm going to do is establish its visibility and usage. Is it an exported symbol? And if so, who's actually calling it? Understanding the caller and its context is crucial. Knowing if this thing is widely accessible or more restricted to certain parts of the kernel is the next step to knowing its importance and how to debug it! I want to map the calling context. |
| 20/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "vfs_coredump"
}Results: {
"Output": "Documentation/security/credentials.rst=555=different set of credentials. This is done in the following places:\n--\nDocumentation/security/credentials.rst-557- * ``sys_faccessat()``.\nDocumentation/security/credentials.rst:558: * ``vfs_coredump()``.\nDocumentation/security/credentials.rst-559- * nfs4recover.c.\n--\nDocumentation/translations/zh_CN/security/credentials.rst=254=const指针上操作,因此不需要进行类型转换,但需要临时放弃const限定,以便能够修改\n--\nDocumentation/translations/zh_CN/security/credentials.rst-477- * ``sys_faccessat()``.\nDocumentation/translations/zh_CN/security/credentials.rst:478: * ``vfs_coredump()``.\nDocumentation/translations/zh_CN/security/credentials.rst-479- * nfs4recover.c.\n--\nfs/coredump.c=1194=static void do_coredump(struct core_name *cn, struct coredump_params *cprm,\n--\nfs/coredump.c-1266-\nfs/coredump.c:1267:void vfs_coredump(const kernel_siginfo_t *siginfo)\nfs/coredump.c-1268-{\n--\ninclude/linux/coredump.h=67=bool dump_user_range(struct coredump_params *cprm, unsigned long start,\ninclude/linux/coredump.h-68-\t\t unsigned long len);\ninclude/linux/coredump.h:69:void vfs_coredump(const kernel_siginfo_t *siginfo);\ninclude/linux/coredump.h-70-\n--\ninclude/linux/coredump.h-88-#else\ninclude/linux/coredump.h:89:static inline void vfs_coredump(const kernel_siginfo_t *siginfo) {}\ninclude/linux/coredump.h-90-\n--\nkernel/signal.c=2810=bool get_signal(struct ksignal *ksig)\n--\nkernel/signal.c-3030-\t\t\t */\nkernel/signal.c:3031:\t\t\tvfs_coredump(\u0026ksig-\u003einfo);\nkernel/signal.c-3032-\t\t}\n"
} |
| 21/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 44609 output: 30 thoughts: 115 Thoughts: **My Core Dump Investigation** Okay, so `vfs_coredump` is called right from `get_signal` in `kernel/signal.c`. That's the normal path for when a process goes belly up, so it's *definitely* reachable. Good to confirm the obvious, but now let's figure out what needs to be enabled for it to *actually* do its thing. First thought is, obviously, `CONFIG_COREDUMP`. Gotta have that enabled, or `vfs_coredump` will just sit there like a lump. Then there's `CONFIG_UNIX` – that's gotta be on for `coredump_socket` to even be available. But, hold on a second... Are there any *other* hidden dependencies? I should probably take another look through `fs/coredump.c`, just to be sure I'm not missing something subtle. Better safe than sorry. |
| 22/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "#ifdef CONFIG_",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=665=static_assert(sizeof(struct coredump_record_header) == COREDUMP_RECORD_HEADER_SIZE_VER0);\nfs/coredump.c-666-\nfs/coredump.c:667:#ifdef CONFIG_UNIX\nfs/coredump.c-668-/* af_unix halves the send buffer to size a single skb. */\n--\nfs/coredump.c=1499=EXPORT_SYMBOL(dump_skip);\nfs/coredump.c-1500-\nfs/coredump.c:1501:#ifdef CONFIG_ELF_CORE\nfs/coredump.c-1502-static bool dump_emit_page(struct coredump_params *cprm, struct page *page)\n--\nfs/coredump.c=1633=EXPORT_SYMBOL(dump_align);\nfs/coredump.c-1634-\nfs/coredump.c:1635:#ifdef CONFIG_SYSCTL\nfs/coredump.c-1636-\n--\nfs/coredump.c=1715=static char core_modes[] = {\nfs/coredump.c-1716-\t\"file\\npipe\"\nfs/coredump.c:1717:#ifdef CONFIG_UNIX\nfs/coredump.c-1718-\t\"\\nsocket\"\n"
} |
| 23/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45300 output: 18 thoughts: 44 Thoughts: Okay, here's my interpretation of those thoughts, summarized from my perspective as someone familiar with kernel configuration: **Core Dump Configuration Deep Dive** Alright, so I'm looking at the kernel's core dump functionality and the environment it operates in. I'm focusing on the main configuration options, and right off the bat, `CONFIG_COREDUMP` and `CONFIG_UNIX` are clearly standing out as the critical ones to be aware of. They're foundational, no doubt. The question then arises if the finer grained option of `CONFIG_ELF_CORE` is strictly required. It's time to dig into the details to understand whether that's necessary. I'll need to check the relevant Kconfig file, so I'll go take a look at `fs/Kconfig.binfmt` to see how those options are related. It should clarify the relationships and potential dependencies. |
| 24/2 | 2026/08/21 13:50 | tool | grepper |
0mArgs:{
"Expression": "config ELF_CORE"
}Results: {
"Output": "init/Kconfig=1800=config BUG\n--\ninit/Kconfig-1809-\ninit/Kconfig:1810:config ELF_CORE\ninit/Kconfig-1811-\tdepends on COREDUMP\n"
} |
| 25/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45623 output: 33 thoughts: 29 Thoughts: Here's my thought process, as it stands: **Debugging the Kernel: Dependency Analysis** Okay, so I see `ELF_CORE` mentioned, and my first instinct is to understand its dependencies. It looks like it directly depends on `COREDUMP`, which makes sense. I'll need to confirm this quickly though, especially since I'm trying to optimize the kernel build and configuration. The best place to find authoritative info is always `init/Kconfig`. Time to dive in there. I'll want to quickly confirm this dependency and possibly check for any other related config options while I'm at it. Let's see what `init/Kconfig` has to say. |
| 26/2 | 2026/08/21 13:50 | tool | read-file |
0mArgs:{
"File": "init/Kconfig",
"FirstLine": 1810,
"LineCount": 10
}Results: {
"Contents": "1810:\tconfig ELF_CORE\n1811:\t\tdepends on COREDUMP\n1812:\t\tdefault y\n1813:\t\tbool \"Enable ELF core dumps\" if EXPERT\n1814:\t\thelp\n1815:\t\t Enable support for generating core dumps. Disabling saves about 4k.\n1816:\t\n1817:\t\n1818:\tconfig PCSPKR_PLATFORM\n1819:\t\tbool \"Enable PC-Speaker support\" if EXPERT\n"
} |
| 27/2 | 2026/08/21 13:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45969 output: 16 thoughts: 19 Thoughts: Here's my thinking: **Examining Default Configuration Options** Okay, so `ELF_CORE` defaults to 'y'. That's the baseline, the expected behavior. Now, I need to consider a potential intersection with another option, something that might influence or interact with this default setting. My mind immediately jumps to `CONFIG_UNIX`. Is there a specific relationship between those two? Does enabling `CONFIG_UNIX` potentially alter or override the default behavior of `ELF_CORE` in any way? This is what I need to investigate. I need to understand how these two configuration options might be related. Could there be implicit dependencies, or mutually exclusive conditions? Let's check the code and documentation. |
| 28/2 | 2026/08/21 13:51 | tool | grepper |
0mArgs:{
"Expression": "config UNIX"
}Results: {
"Output": "block/partitions/Kconfig=148=config SOLARIS_X86_PARTITION\n--\nblock/partitions/Kconfig-157-\nblock/partitions/Kconfig:158:config UNIXWARE_DISKLABEL\nblock/partitions/Kconfig-159-\tbool \"Unixware slices support\"\n--\ndrivers/tty/Kconfig=78=config VT_HW_CONSOLE_BINDING\n--\ndrivers/tty/Kconfig-93-\ndrivers/tty/Kconfig:94:config UNIX98_PTYS\ndrivers/tty/Kconfig-95-\tbool \"Unix98 PTY support\" if EXPERT\n--\nnet/unix/Kconfig-5-\nnet/unix/Kconfig:6:config UNIX\nnet/unix/Kconfig-7-\tbool \"Unix domain sockets\"\n--\nnet/unix/Kconfig=19=config\tAF_UNIX_OOB\n--\nnet/unix/Kconfig-25-\nnet/unix/Kconfig:26:config UNIX_DIAG\nnet/unix/Kconfig-27-\ttristate \"UNIX: socket monitoring interface\"\n"
} |
| 29/2 | 2026/08/21 13:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 46431 output: 27 thoughts: 528 Thoughts: **Analyzing Coredump Socket Implementation for API Exposure** Okay, so I'm looking at this coredump socket functionality and trying to identify the best entry point for external use, keeping in mind the preference for non-static, non-inlined functions. I see `CONFIG_UNIX` is relevant, coming from `net/unix/Kconfig`, and that `COREDUMP` and `UNIX` are the configurations. I started by listing the key functions related to the coredump process: `coredump_sock_request`, `vma_dump_size`, `do_coredump`, and `vfs_coredump`. The goal is to figure out which is *the* function to expose. I quickly checked the static status. `coredump_sock_request` is static, as is `coredump_socket`. Same with `do_coredump` and `vma_dump_size`. So, that leaves `vfs_coredump` as non-static. The prompt, though, specifically emphasizes preferring non-static, non-inlined API entrypoint functions. So it seems `vfs_coredump` is a good candidate. I can see the only changes are to `fs/coredump.c` and some tests. I checked all the modified functions. So, in `fs/coredump.c`, I have: `coredump_sock_send` (static inline), `coredump_sock_mark` (static inline), `coredump_sock_shutdown` (static inline), `coredump_sock_request` (static), `do_coredump` (static), `vfs_coredump` (non-static), `vma_dump_size` (static), and `dump_vma_snapshot` (static). Therefore, `vfs_coredump` seems to be the obvious choice here, since the guidance leans towards the non-static functions. Let me also consider entry points. The crash dump via the socket protocol is triggered when a process crashes and the `core_pattern` is set to something like `|/some/program` or `socket`. I see the test is setting `core_pattern` to `@@/tmp/coredump.socket`. Ah, that's it - `core_pattern` starting with `@@` triggers the socket protocol. Alright, now I should look at the `core_pattern` parsing logic. |
| 30/2 | 2026/08/21 13:51 | tool | grepper |
0mError:no matches Args: {
"Expression": "@@",
"PathPrefix": "fs/coredump.c"
}Results: {
"error": "no matches"
} |
| 31/2 | 2026/08/21 13:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 47516 output: 29 thoughts: 16 Thoughts: Here's my summary, as though I'm thinking through this process: **Diving into the Core Dump Code** Alright, time to dig in. I need to understand how the kernel's coredump functionality works. My first instinct, given my experience, is to head directly for the core of the matter, so to speak. Let's start with `fs/coredump.c`. It's the logical place to begin, the main file for filesystem-level core dump handling. I'm going to scan the code looking for those specific markers, the `@@` pattern. I remember that's often used as a debugging aid or a point where a developer inserts placeholder code or comments. Finding those instances will give me a quick entry point to understanding the logic flow. I'm hoping to use those to trace the code path related to creating the core dumps, what information is included in it and how. Let's see what we find. |
| 32/2 | 2026/08/21 13:51 | tool | grepper |
0mArgs:{
"Expression": "core_pattern",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=85=static unsigned int core_sort_vma;\nfs/coredump.c:86:static char core_pattern[CORENAME_MAX_SIZE] = \"core\";\nfs/coredump.c-87-static int core_name_size = CORENAME_MAX_SIZE;\n--\nfs/coredump.c=238=static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm,\n--\nfs/coredump.c-241-\tconst struct cred *cred = current_cred();\nfs/coredump.c:242:\tconst char *pat_ptr = core_pattern;\nfs/coredump.c-243-\tbool was_space = false;\n--\nfs/coredump.c-264-\tcase COREDUMP_PIPE: {\nfs/coredump.c:265:\t\tint argvs = sizeof(core_pattern) / 2;\nfs/coredump.c-266-\t\t(*argv) = kmalloc_objs(**argv, argvs);\n--\nfs/coredump.c-471-\t *\nfs/coredump.c:472:\t * If core_pattern does not include a %p (as is the default)\nfs/coredump.c-473-\t * and core_uses_pid is set, then .%pid will be appended to\n--\nfs/coredump.c=1063=static bool coredump_pipe(struct core_name *cn, struct coredump_params *cprm,\n--\nfs/coredump.c-1076-\t\t * consistent way to catch recursive crashes.\nfs/coredump.c:1077:\t\t * We can still crash if the core_pattern binary sets\nfs/coredump.c-1078-\t\t * RLIM_CORE = !1, but it runs as root, and can do\n--\nfs/coredump.c-1083-\t\t * right pid if a thread in a multi-threaded\nfs/coredump.c:1084:\t\t * core_pattern process dies.\nfs/coredump.c-1085-\t\t */\n--\nfs/coredump.c=1637=void validate_coredump_safety(void)\n--\nfs/coredump.c-1639-\tif (suid_dumpable == TASK_DUMPABLE_ROOT \u0026\u0026\nfs/coredump.c:1640:\t core_pattern[0] != '/' \u0026\u0026 core_pattern[0] != '|' \u0026\u0026 core_pattern[0] != '@') {\nfs/coredump.c-1641-\nfs/coredump.c:1642:\t\tcoredump_report_failure(\"Unsafe core_pattern used with fs.suid_dumpable=2: \"\nfs/coredump.c-1643-\t\t\t\"pipe handler or fully qualified core dump path required. \"\nfs/coredump.c:1644:\t\t\t\"Set kernel.core_pattern before fs.suid_dumpable.\");\nfs/coredump.c-1645-\t}\n--\nfs/coredump.c=1648=static inline bool check_coredump_socket(void)\n--\nfs/coredump.c-1651-\nfs/coredump.c:1652:\tif (core_pattern[0] != '@')\nfs/coredump.c-1653-\t\treturn true;\n--\nfs/coredump.c-1663-\t/* Must be an absolute path... */\nfs/coredump.c:1664:\tif (core_pattern[1] != '/') {\nfs/coredump.c-1665-\t\t/* ... or the socket request protocol... */\nfs/coredump.c:1666:\t\tif (core_pattern[1] != '@')\nfs/coredump.c-1667-\t\t\treturn false;\nfs/coredump.c-1668-\t\t/* ... and if so must be an absolute path. */\nfs/coredump.c:1669:\t\tif (core_pattern[2] != '/')\nfs/coredump.c-1670-\t\t\treturn false;\nfs/coredump.c:1671:\t\tp = \u0026core_pattern[2];\nfs/coredump.c-1672-\t} else {\nfs/coredump.c:1673:\t\tp = \u0026core_pattern[1];\nfs/coredump.c-1674-\t}\n--\nfs/coredump.c-1680-\t/* Must not contain \"..\" in the path. */\nfs/coredump.c:1681:\tif (name_contains_dotdot(core_pattern))\nfs/coredump.c-1682-\t\treturn false;\n--\nfs/coredump.c=1687=static int proc_dostring_coredump(const struct ctl_table *table, int write,\n--\nfs/coredump.c-1691-\tssize_t retval;\nfs/coredump.c:1692:\tchar old_core_pattern[CORENAME_MAX_SIZE];\nfs/coredump.c-1693-\n--\nfs/coredump.c-1696-\nfs/coredump.c:1697:\tretval = strscpy(old_core_pattern, core_pattern, CORENAME_MAX_SIZE);\nfs/coredump.c-1698-\n--\nfs/coredump.c-1703-\tif (!check_coredump_socket()) {\nfs/coredump.c:1704:\t\tstrscpy(core_pattern, old_core_pattern, retval + 1);\nfs/coredump.c-1705-\t\treturn -EINVAL;\n--\nfs/coredump.c-1707-\nfs/coredump.c:1708:\tif (strncmp(old_core_pattern, core_pattern, CORENAME_MAX_SIZE))\nfs/coredump.c-1709-\t\tvalidate_coredump_safety();\n--\nfs/coredump.c=1722=static const struct ctl_table coredump_sysctls[] = {\n--\nfs/coredump.c-1730-\t{\nfs/coredump.c:1731:\t\t.procname\t= \"core_pattern\",\nfs/coredump.c:1732:\t\t.data\t\t= core_pattern,\nfs/coredump.c-1733-\t\t.maxlen\t\t= CORENAME_MAX_SIZE,\n"
} |
| 33/2 | 2026/08/21 13:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 49370 output: 20 thoughts: 321 Thoughts: **My Assessment of the Core Dump Socket Protocol Patch** Okay, so this patch introduces a functional change in the kernel's core dump mechanism, specifically related to the socket protocol triggered by `core_pattern` starting with `@@`. This socket protocol is implemented within `coredump_sock_request`, and the patch extends it by adding `COREDUMP_MEMORY_TYPES`. Given this, and the fact that the socket protocol becomes active when `core_pattern` is configured, say, as `@@/tmp/coredump.socket`, this screams "fuzz me!" I immediately see this as a juicy target for vulnerability research. I need to confirm the necessary configurations though, let's see, I think we have: `CONFIG_COREDUMP`, `CONFIG_UNIX`, and `CONFIG_ELF_CORE` (or perhaps `CONFIG_BINFMT_ELF`). I need to check these thoroughly. Right, let's see how `CONFIG_ELF_CORE` comes into play. From what I recall, `vfs_coredump` calls `do_coredump`, and `do_coredump` dispatches to `coredump_socket` if we're using the socket protocol. But wait, I'm second-guessing myself. Does it actually *need* `CONFIG_ELF_CORE`? I think it does... `do_coredump` iterates through the available `binfmt`s and calls their `core_dump` function. For ELF binaries, that's `elf_core_dump` in `fs/binfmt_elf.c`. `elf_core_dump` then calls `coredump_write` which then calls `dump_vma_snapshot`. So, yes, it seems either `CONFIG_ELF_CORE` or potentially `CONFIG_BINFMT_ELF` is definitely required to use ELF core dumps with the socket protocol. I should double-check `fs/Kconfig.binfmt` to be absolutely certain on this `BINFMT_ELF` option. I should confirm the dependency on the `binfmt` configuration. |
| 34/2 | 2026/08/21 13:51 | tool | grepper |
0mArgs:{
"Expression": "config BINFMT_ELF"
}Results: {
"Output": "fs/Kconfig.binfmt=3=menu \"Executable file formats\"\nfs/Kconfig.binfmt-4-\nfs/Kconfig.binfmt:5:config BINFMT_ELF\nfs/Kconfig.binfmt-6-\tbool \"Kernel support for ELF binaries\"\n--\nfs/Kconfig.binfmt-30-\nfs/Kconfig.binfmt:31:config BINFMT_ELF_KUNIT_TEST\nfs/Kconfig.binfmt-32-\tbool \"Build KUnit tests for ELF binary support\" if !KUNIT_ALL_TESTS\n--\nfs/Kconfig.binfmt=55=config ARCH_USE_GNU_PROPERTY\n--\nfs/Kconfig.binfmt-57-\nfs/Kconfig.binfmt:58:config BINFMT_ELF_FDPIC\nfs/Kconfig.binfmt-59-\tbool \"Kernel support for FDPIC ELF binaries\"\n"
} |
| 35/2 | 2026/08/21 13:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 50381 output: 49 thoughts: 401 Thoughts: **Analyzing Coredump Configuration and Entry Points** Okay, so I'm looking at this coredump-related patch, and the key configurations seem to be `COREDUMP`, `UNIX`, `BINFMT_ELF`, and `ELF_CORE`. I need to ensure I've got the essential components covered. Now, the patch focuses on `vma_dump_size`, which is interesting, especially the conditional checks for `vma_is_dax(vma)` and `is_vm_hugetlb_page(vma)`. Those are enabled by `CONFIG_DAX` and `CONFIG_HUGETLB_PAGE`, but the core of the logic isn't dependent on those, it just handles them if they're there. So, `COREDUMP`, `UNIX`, `BINFMT_ELF`, and `ELF_CORE` are the key configs that drive the behavior. Now, I need to pinpoint the critical symbols here. `vfs_coredump` is the coredump entry point, that's what I'm looking for. It's the front door. `coredump_sock_request` is static, which is a key modification point, but I need to focus on what the patch is changing. The instructions are clear – prefer non-static, non-inlined API entrypoints. That means `vfs_coredump` is the best choice, right? It's the exported entry. Let's make sure. `do_coredump` is static, but might not be inlined because it's a large function. But I should check if `vfs_coredump` is the only non-static function. Aha, yes, it seems to be! `dump_vma_snapshot`? No, it's static. `vma_dump_size`? Static, as expected. I should also check, to make sure I haven't missed something, if there are any other non-static functions in `fs/coredump.c`. |
| 36/2 | 2026/08/21 13:51 | tool | grepper |
0mArgs:{
"Expression": "^[a-zA-Z0-9_]+ [a-zA-Z0-9_]+\\(",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=105=static int expand_corename(struct core_name *cn, int size)\n--\nfs/coredump.c-122-\nfs/coredump.c:123:static __printf(2, 0) int cn_vprintf(struct core_name *cn, const char *fmt,\nfs/coredump.c-124-\t\t\t\t va_list arg)\n--\nfs/coredump.c-146-\nfs/coredump.c:147:static __printf(2, 3) int cn_printf(struct core_name *cn, const char *fmt, ...)\nfs/coredump.c-148-{\n--\nfs/coredump.c-158-\nfs/coredump.c:159:static __printf(2, 3)\nfs/coredump.c:160:int cn_esc_printf(struct core_name *cn, const char *fmt, ...)\nfs/coredump.c-161-{\n--\nfs/coredump.c=1194=static void do_coredump(struct core_name *cn, struct coredump_params *cprm,\n--\nfs/coredump.c-1266-\nfs/coredump.c:1267:void vfs_coredump(const kernel_siginfo_t *siginfo)\nfs/coredump.c-1268-{\n--\nfs/coredump.c=1469=static bool dump_flush_skip(struct coredump_params *cprm)\n--\nfs/coredump.c-1478-\nfs/coredump.c:1479:bool dump_emit(struct coredump_params *cprm, const void *addr, int nr)\nfs/coredump.c-1480-{\n--\nfs/coredump.c=1485=EXPORT_SYMBOL(dump_emit);\nfs/coredump.c-1486-\nfs/coredump.c:1487:void dump_skip_to(struct coredump_params *cprm, unsigned long pos)\nfs/coredump.c-1488-{\n--\nfs/coredump.c=1493=EXPORT_SYMBOL(dump_skip_to);\nfs/coredump.c-1494-\nfs/coredump.c:1495:void dump_skip(struct coredump_params *cprm, size_t nr)\nfs/coredump.c-1496-{\n--\nfs/coredump.c=1553=static inline struct page *dump_page_copy(struct page *src, struct page *dst)\n--\nfs/coredump.c-1558-\nfs/coredump.c:1559:bool dump_user_range(struct coredump_params *cprm, unsigned long start,\nfs/coredump.c-1560-\t\t unsigned long len)\n--\nfs/coredump.c-1623-\nfs/coredump.c:1624:bool dump_align(struct coredump_params *cprm, int align)\nfs/coredump.c-1625-{\n--\nfs/coredump.c=1633=EXPORT_SYMBOL(dump_align);\n--\nfs/coredump.c-1636-\nfs/coredump.c:1637:void validate_coredump_safety(void)\nfs/coredump.c-1638-{\n"
} |
| 37/2 | 2026/08/21 13:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 52122 output: 17 thoughts: 363 Thoughts: **My Core Dump Strategy** Okay, so I'm looking at how this core dump functionality is initiated. `vfs_coredump` is the primary entry point, the main function. It seems like `dump_user_range` is also exported and used by `binfmt_elf`, but `vfs_coredump` is the one that really kicks off the process. So, I'll definitely use `vfs_coredump` as the starting point for my analysis. Let me see if there are any other functions that are critical to understanding this. Hmm, the patch modifies `coredump_sock_request` and `vma_dump_size`, but those are static functions. They're probably inlined somewhere. But `vfs_coredump` is definitely not inlined. Good, I'll use `vfs_coredump` as the key symbol to focus on. Alright, let's review the relevant configurations again to make sure I'm not missing anything. I have: `COREDUMP`, `UNIX`, `BINFMT_ELF`, and `ELF_CORE`. I need to ensure these are all valid. Let's check `ELF_CORE`. Yep, it's defined in `init/Kconfig`. Good. `BINFMT_ELF`? Found it in `fs/Kconfig.binfmt`. Check. What about `UNIX`? That's in `net/unix/Kconfig`. And `COREDUMP`? Also in `fs/Kconfig.binfmt`. Excellent. All valid and accounted for. Are there any other configurations that might be important? The tests are using `socketpair`, `accept4`, `fork`, `waitpid`, and `get_pidfd_info`. Hmm, `get_pidfd_info`... I bet I need `PIDFD` enabled to support those tests. Let me check if that's a valid configuration option. |
| 38/2 | 2026/08/21 13:51 | tool | grepper |
0mError:no matches Args: {
"Expression": "config PIDFD"
}Results: {
"error": "no matches"
} |
| 39/2 | 2026/08/21 13:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 52930 output: 18 thoughts: 18 Thoughts: Here's my take on those thoughts: **Checking Kernel Configuration for PIDFD Support** Okay, so I'm thinking about how to confirm if `PIDFD` is enabled. It's not something I deal with every day, so my first instinct is to go straight to the source. Specifically, I need to check the kernel configuration files. The usual suspects for this kind of feature are the `init/Kconfig` file, or perhaps a similar configuration file in a related subsystem. I need to quickly determine whether the kernel has been built with `PIDFD` support, and this seems like the most direct and efficient way to do it. The Kconfig files are where the feature options are defined and controlled, so that's where I need to look. I'll get the answer there and be done with it. |
| 40/2 | 2026/08/21 13:51 | tool | grepper |
0mArgs:{
"Expression": "config.*PIDFD"
}Results: {
"Output": "samples/Kconfig=165=config SAMPLE_LANDLOCK\n--\nsamples/Kconfig-171-\nsamples/Kconfig:172:config SAMPLE_PIDFD\nsamples/Kconfig-173-\tbool \"pidfd sample\"\n"
} |
| 41/2 | 2026/08/21 13:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53209 output: 17 thoughts: 57 Thoughts: **My Initial Thoughts on PIDFD Configuration** Okay, so I'm looking into the PIDFD functionality, and it seems like there isn't a dedicated, standalone configuration option for it. It's likely either a default feature, always enabled, or perhaps subtly integrated as part of something broader like `CONFIG_PROC_FS`. That would make sense, given its process-related nature. Now, let's pivot and consider the broader context, specifically how coredumps are handled. I need to make sure everything is properly set up from that perspective. The obvious first step is to confirm the presence of `CONFIG_SYSCTL`, because we know that `core_pattern` within sysctl is *essential* for defining how core dumps are named or piped to a utility. |
| 42/2 | 2026/08/21 13:52 | tool | grepper |
0mArgs:{
"Expression": "config SYSCTL"
}Results: {
"Output": "init/Kconfig=1661=config LD_ORPHAN_WARN_LEVEL\n--\ninit/Kconfig-1666-\ninit/Kconfig:1667:config SYSCTL\ninit/Kconfig-1668-\tbool\n--\ninit/Kconfig=1670=config HAVE_UID16\n--\ninit/Kconfig-1672-\ninit/Kconfig:1673:config SYSCTL_EXCEPTION_TRACE\ninit/Kconfig-1674-\tbool\n--\ninit/Kconfig-1677-\ninit/Kconfig:1678:config SYSCTL_ARCH_UNALIGN_NO_WARN\ninit/Kconfig-1679-\tbool\n--\ninit/Kconfig-1684-\ninit/Kconfig:1685:config SYSCTL_ARCH_UNALIGN_ALLOW\ninit/Kconfig-1686-\tbool\n--\nlib/Kconfig.debug=2804=config RESOURCE_KUNIT_TEST\n--\nlib/Kconfig.debug-2816-\nlib/Kconfig.debug:2817:config SYSCTL_KUNIT_TEST\nlib/Kconfig.debug-2818-\ttristate \"KUnit test for sysctl\" if !KUNIT_ALL_TESTS\n"
} |
| 43/2 | 2026/08/21 13:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 53769 output: 18 thoughts: 65 Thoughts: Okay, here's my take on those considerations: **My Kernel Configuration Checklist** Right, `SYSCTL` is almost always enabled these days – can't imagine a system running without it, so that's a given. Now, let's nail down this coredump setup. I'm focusing on the essential configs here. We're looking at `COREDUMP`, which is a must, obviously. And then we have `UNIX`, `BINFMT_ELF`, and `ELF_CORE` -- these are the obvious players in handling core dumps in a standard Linux environment. The thing that bugs me... I need to double-check something. Is `CONFIG_PROC_FS` actually *required* if I want to use `/proc/self/coredump_filter`? I can't immediately recall if that's a hard dependency, or if there's a more nuanced relationship there. It's time to dig into the kernel docs and refresh my memory on the `/proc` filesystem interactions. Gotta make sure those settings are airtight. |
| 44/2 | 2026/08/21 13:52 | tool | grepper |
0mArgs:{
"Expression": "config PROC_FS"
}Results: {
"Output": "fs/proc/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/proc/Kconfig:2:config PROC_FS\nfs/proc/Kconfig-3-\tbool \"/proc file system support\" if EXPERT\n"
} |
| 45/2 | 2026/08/21 13:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 54147 output: 32 thoughts: 221 Thoughts: **My Configuration Checklist** Okay, so I'm putting together the necessary kernel configurations for this test, and it's pretty straightforward. First, I definitely need `PROC_FS` because the test explicitly uses `/proc/self/coredump_filter` and `/proc/<pid>/mem`. That's a no-brainer. Now, about `CONFIG_TMPFS`... the test does use `fd_tmpfs_detached`, but that's likely okay as is. Nothing more to see there. Let me recap what I've got so far: the important configurations I'm working with are `COREDUMP`, `UNIX`, `BINFMT_ELF`, `ELF_CORE`, and of course, `PROC_FS`. Hang on, I need to double-check something about `CONFIG_UNIX`. Is it just a binary config, or is it a tristate option? Yep, confirmed: it's `tristate "Unix domain sockets"`. However, the code uses `coredump_socket`, which is only compiled if `CONFIG_UNIX` is enabled. Which makes me wonder, if `CONFIG_UNIX` is modular (`m`), does `coredump_socket` get compiled? I'd better double-check `fs/coredump.c` to be certain. |
| 46/2 | 2026/08/21 13:52 | tool | grepper |
0mArgs:{
"Expression": "#ifdef CONFIG_UNIX",
"PathPrefix": "fs/coredump.c"
}Results: {
"Output": "fs/coredump.c=665=static_assert(sizeof(struct coredump_record_header) == COREDUMP_RECORD_HEADER_SIZE_VER0);\nfs/coredump.c-666-\nfs/coredump.c:667:#ifdef CONFIG_UNIX\nfs/coredump.c-668-/* af_unix halves the send buffer to size a single skb. */\n--\nfs/coredump.c=1715=static char core_modes[] = {\nfs/coredump.c-1716-\t\"file\\npipe\"\nfs/coredump.c:1717:#ifdef CONFIG_UNIX\nfs/coredump.c-1718-\t\"\\nsocket\"\n"
} |
| 47/2 | 2026/08/21 13:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 54870 output: 28 thoughts: 59 Thoughts: **Checking Kconfig Definitions** Okay, so I'm looking at this code and I have a nagging feeling about `#ifdef CONFIG_UNIX`. It just doesn't feel quite right. If `CONFIG_UNIX` is set to "m," meaning it's a module, then `#ifdef CONFIG_UNIX` might not actually be the right check. Shouldn't it be something like `#ifdef CONFIG_UNIX_MODULE`? But hold on a second... isn't `CONFIG_UNIX` actually defined as a boolean somewhere? I need to double-check the `net/unix/Kconfig` file, just to be absolutely sure what kind of variable we're dealing with here. Gotta make sure I'm using the right macro. |
| 48/2 | 2026/08/21 13:52 | tool | grepper |
0mArgs:{
"Expression": "config UNIX",
"PathPrefix": "net/unix/Kconfig"
}Results: {
"Output": "net/unix/Kconfig-5-\nnet/unix/Kconfig:6:config UNIX\nnet/unix/Kconfig-7-\tbool \"Unix domain sockets\"\n--\nnet/unix/Kconfig=19=config\tAF_UNIX_OOB\n--\nnet/unix/Kconfig-25-\nnet/unix/Kconfig:26:config UNIX_DIAG\nnet/unix/Kconfig-27-\ttristate \"UNIX: socket monitoring interface\"\n"
} |
| 49/2 | 2026/08/21 13:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 55242 output: 139 thoughts: 419 Thoughts: **Analyzing the Core Dump Flow** Okay, so `CONFIG_UNIX` is a boolean, a straightforward `y` or `n`. Let me see what other configurations are relevant here. I've spotted that `CONFIG_MMU` is used, specifically within `vma_dump_size`. `CONFIG_DAX` and `CONFIG_HUGETLB_PAGE` are also in the mix, although they don't seem critical to the core dumping process itself. I'll keep a mental note of those configurations, but I think the ones to focus on are: `COREDUMP`, `UNIX`, `BINFMT_ELF`, `ELF_CORE`, and `PROC_FS`. Now, let's confirm that `vfs_coredump` is the right function to center on. Yes, it looks like it's the main entry point for initiating a coredump. I'll also add `do_coredump` to the list, just in case, even though it's static. Hmm, no, `vfs_coredump` is the key one. Are there any other non-static functions related to coredumps that could be important? I see `dump_user_range`, `dump_emit`, `dump_skip_to`, `dump_skip`, and `dump_align`. Those are all exported, but `vfs_coredump` is still the initiator. Let me revisit that patch... Okay, I see. The patch touches `vma_dump_size` and `coredump_sock_request`. `vma_dump_size` is called by `dump_vma_snapshot`, which in turn is called by `coredump_write`. `coredump_write` is called by `elf_core_dump`, which is finally called by `do_coredump`. And, as we established earlier, `do_coredump` is called by `vfs_coredump`. Therefore, `vfs_coredump` is indeed the root of the call graph. That's the function I'll focus on. I'll make sure to note all the interconnections. Okay, let's call `set-results`. |
| 50/2 | 2026/08/21 13:52 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"COREDUMP",
"UNIX",
"BINFMT_ELF",
"ELF_CORE",
"PROC_FS"
],
"FocusSymbols": [
"vfs_coredump"
],
"Reasoning": "The patch modifies the coredump socket protocol to allow the coredump server to dynamically select memory types to include in the core dump. This is a functional change in the core kernel's coredump logic, which is reachable when a process crashes and the core_pattern is set to use a socket. The changes are definitely worth fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"COREDUMP",
"UNIX",
"BINFMT_ELF",
"ELF_CORE",
"PROC_FS"
],
"FocusSymbols": [
"vfs_coredump"
],
"Reasoning": "The patch modifies the coredump socket protocol to allow the coredump server to dynamically select memory types to include in the core dump. This is a functional change in the core kernel's coredump logic, which is reachable when a process crashes and the core_pattern is set to use a socket. The changes are definitely worth fuzzing.",
"WorthFuzzing": true
} |
| 51/1 | 2026/08/21 13:52 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 52/2 | 2026/08/21 13:52 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch modifies the coredump socket protocol to allow dynamic selection of memory types by introducing new fields to `struct coredump_req` and `struct coredump_ack`. \n\nThe `struct coredump_req` is sent to user space, but it is fully initialized on the stack using a designated initializer (`= { ... }`), which ensures all fields are properly initialized. Furthermore, the structure is perfectly aligned and contains no padding bytes (4+4+8+8+8 = 32 bytes), eliminating any risk of padding info-leaks.\n\nThe `struct coredump_ack` is received from user space, and it is explicitly zero-initialized (`= {}`) before the `recv` call, ensuring that any unreceived fields (e.g., when a shorter struct is sent by an older userspace) remain zeroed and do not contain uninitialized stack memory.\n\nThere are no new buffer allocations, no complex data structures with uninitialized fields, and no conditional logic depending on uninitialized variables. Any potential memory safety issues (like out-of-bounds accesses) would be caught by standard KASAN. Therefore, a dedicated KMSAN fuzzing session is not necessary.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit 324a9577c7e70c4a55951ad6619bab74d40f523a
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Aug 21 13:49:49 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst
index 2ccd5b2dfdd33..3a309e8ee96bd 100644
--- a/Documentation/filesystems/proc.rst
+++ b/Documentation/filesystems/proc.rst
@@ -1962,6 +1962,10 @@ For example::
$ echo 0x7 > /proc/self/coredump_filter
$ ./some_program
+If the coredump socket protocol is used a coredump server can select memory
+types to include dynamically. See COREDUMP_MEMORY_TYPES in
+include/uapi/linux/coredump.h.
+
3.5 /proc/<pid>/mountinfo - Information about mounts
--------------------------------------------------------
diff --git a/fs/coredump.c b/fs/coredump.c
index 7b568d25887ce..ec9359e382ea5 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -759,10 +759,39 @@ static inline bool coredump_sock_send(struct file *file, struct coredump_req *re
return ret == sizeof(*req);
}
-static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER0);
-static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER0);
+static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER1);
+static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER1);
static_assert(sizeof(enum coredump_mark) == sizeof(__u32));
+/* Every memory type this kernel knows. */
+#define COREDUMP_MEMORY_ALL \
+ (COREDUMP_MEMORY_ANON_PRIVATE | COREDUMP_MEMORY_ANON_SHARED | \
+ COREDUMP_MEMORY_FILE_PRIVATE | COREDUMP_MEMORY_FILE_SHARED | \
+ COREDUMP_MEMORY_ELF_HEADERS | \
+ COREDUMP_MEMORY_HUGETLB_PRIVATE | COREDUMP_MEMORY_HUGETLB_SHARED | \
+ COREDUMP_MEMORY_DAX_PRIVATE | COREDUMP_MEMORY_DAX_SHARED)
+
+#define COREDUMP_MEMORY_TYPE_BIT(mmf) BIT((mmf) - MMF_DUMP_FILTER_SHIFT)
+static_assert(COREDUMP_MEMORY_ALL == (MMF_DUMP_FILTER_MASK >> MMF_DUMP_FILTER_SHIFT));
+static_assert(COREDUMP_MEMORY_ANON_PRIVATE ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ANON_PRIVATE));
+static_assert(COREDUMP_MEMORY_ANON_SHARED ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ANON_SHARED));
+static_assert(COREDUMP_MEMORY_FILE_PRIVATE ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_MAPPED_PRIVATE));
+static_assert(COREDUMP_MEMORY_FILE_SHARED ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_MAPPED_SHARED));
+static_assert(COREDUMP_MEMORY_ELF_HEADERS ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ELF_HEADERS));
+static_assert(COREDUMP_MEMORY_HUGETLB_PRIVATE ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_HUGETLB_PRIVATE));
+static_assert(COREDUMP_MEMORY_HUGETLB_SHARED ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_HUGETLB_SHARED));
+static_assert(COREDUMP_MEMORY_DAX_PRIVATE ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_DAX_PRIVATE));
+static_assert(COREDUMP_MEMORY_DAX_SHARED ==
+ COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_DAX_SHARED));
+
static inline bool coredump_sock_mark(struct file *file, enum coredump_mark mark)
{
struct msghdr msg = { .msg_flags = MSG_NOSIGNAL };
@@ -804,11 +833,14 @@ static inline void coredump_sock_shutdown(struct file *file)
static bool coredump_sock_request(struct core_name *cn, struct coredump_params *cprm)
{
struct coredump_req req = {
- .size = sizeof(struct coredump_req),
- .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
- COREDUMP_REJECT | COREDUMP_WAIT |
- COREDUMP_RECORDS | COREDUMP_SPARSE,
- .size_ack = sizeof(struct coredump_ack),
+ .size = sizeof(struct coredump_req),
+ .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
+ COREDUMP_REJECT | COREDUMP_WAIT |
+ COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .size_ack = sizeof(struct coredump_ack),
+ .memory_types = cprm->memory_types,
+ .memory_types_mask = COREDUMP_MEMORY_ALL,
};
struct coredump_ack ack = {};
ssize_t usize;
@@ -873,6 +905,30 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
return false;
}
+ if (ack.mask & COREDUMP_MEMORY_TYPES) {
+ /* The memory types need the whole field. */
+ if (usize < COREDUMP_ACK_SIZE_VER1) {
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_MINSIZE);
+ return false;
+ }
+
+ /* The memory types only select what the kernel writes. */
+ if (!(ack.mask & COREDUMP_KERNEL)) {
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_CONFLICTING);
+ return false;
+ }
+
+ /* Refuse unknown memory types. */
+ if (ack.memory_types & ~req.memory_types_mask) {
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_UNSUPPORTED);
+ return false;
+ }
+ } else if (ack.memory_types) {
+ /* Like @spare the field must be zero when it isn't used. */
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_UNSUPPORTED);
+ return false;
+ }
+
/* Record header scratch; a bvec can't point at the stack. */
if (ack.mask & COREDUMP_RECORDS) {
cprm->record_hdr = kmalloc_obj(*cprm->record_hdr);
@@ -880,6 +936,10 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
return false;
}
+ /* The server's selection replaces the task's entirely. */
+ if (ack.mask & COREDUMP_MEMORY_TYPES)
+ cprm->memory_types = ack.memory_types;
+
cprm->mask = ack.mask;
return coredump_sock_mark(cprm->file, COREDUMP_MARK_REQACK);
}
@@ -1200,6 +1260,10 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm,
}
}
+#define COREDUMP_TASK_MEMORY_TYPES(mm) \
+ ((__mm_flags_get_word((mm)) & MMF_DUMP_FILTER_MASK) >> \
+ MMF_DUMP_FILTER_SHIFT)
+
void vfs_coredump(const kernel_siginfo_t *siginfo)
{
size_t *argv __free(kfree) = NULL;
@@ -1211,8 +1275,8 @@ void vfs_coredump(const kernel_siginfo_t *siginfo)
struct coredump_params cprm = {
.siginfo = siginfo,
.limit = rlimit(RLIMIT_CORE),
- /* Snapshot MMF_DUMP_FILTER_* (unlocked) and dumpable for the dump. */
- .mm_flags = __mm_flags_get_word(mm),
+ /* Snapshot the memory types (unlocked) and dumpable for the dump. */
+ .memory_types = COREDUMP_TASK_MEMORY_TYPES(mm),
.dumpable = task_exec_state_get_dumpable(current),
.vma_meta = NULL,
.cpu = raw_smp_processor_id(),
@@ -1746,15 +1810,15 @@ static bool always_dump_vma(struct vm_area_struct *vma)
}
#define DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER 1
+#define COREDUMP_MEMORY_TYPE_INCLUDE(types, type) \
+ ((types) & COREDUMP_MEMORY_##type)
/*
* Decide how much of @vma's contents should be included in a core dump.
*/
static unsigned long vma_dump_size(struct vm_area_struct *vma,
- unsigned long mm_flags)
+ u64 memory_types)
{
-#define FILTER(type) (mm_flags & (1UL << MMF_DUMP_##type))
-
/* always dump the vdso and vsyscall sections */
if (always_dump_vma(vma))
goto whole;
@@ -1764,18 +1828,22 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
/* support for DAX */
if (vma_is_dax(vma)) {
- if ((vma->vm_flags & VM_SHARED) && FILTER(DAX_SHARED))
+ if ((vma->vm_flags & VM_SHARED) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, DAX_SHARED))
goto whole;
- if (!(vma->vm_flags & VM_SHARED) && FILTER(DAX_PRIVATE))
+ if (!(vma->vm_flags & VM_SHARED) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, DAX_PRIVATE))
goto whole;
return 0;
}
/* Hugetlb memory check */
if (is_vm_hugetlb_page(vma)) {
- if ((vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_SHARED))
+ if ((vma->vm_flags & VM_SHARED) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, HUGETLB_SHARED))
goto whole;
- if (!(vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_PRIVATE))
+ if (!(vma->vm_flags & VM_SHARED) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, HUGETLB_PRIVATE))
goto whole;
return 0;
}
@@ -1787,25 +1855,27 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
/* By default, dump shared memory if mapped from an anonymous file. */
if (vma->vm_flags & VM_SHARED) {
if (file_inode(vma->vm_file)->i_nlink == 0 ?
- FILTER(ANON_SHARED) : FILTER(MAPPED_SHARED))
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ANON_SHARED) :
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, FILE_SHARED))
goto whole;
return 0;
}
/* Dump segments that have been written to. */
- if ((!IS_ENABLED(CONFIG_MMU) || vma->anon_vma) && FILTER(ANON_PRIVATE))
+ if ((!IS_ENABLED(CONFIG_MMU) || vma->anon_vma) &&
+ COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ANON_PRIVATE))
goto whole;
if (vma->vm_file == NULL)
return 0;
- if (FILTER(MAPPED_PRIVATE))
+ if (COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, FILE_PRIVATE))
goto whole;
/*
* If this is the beginning of an executable file mapping,
* dump the first page to aid in determining what was mapped here.
*/
- if (FILTER(ELF_HEADERS) &&
+ if (COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ELF_HEADERS) &&
vma->vm_pgoff == 0 && (vma->vm_flags & VM_READ)) {
if ((READ_ONCE(file_inode(vma->vm_file)->i_mode) & 0111) != 0)
return PAGE_SIZE;
@@ -1821,8 +1891,6 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
return DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER;
}
-#undef FILTER
-
return 0;
whole:
@@ -1907,7 +1975,7 @@ static bool dump_vma_snapshot(struct coredump_params *cprm)
m->start = vma->vm_start;
m->end = vma->vm_end;
m->flags = vma->vm_flags;
- m->dump_size = vma_dump_size(vma, cprm->mm_flags);
+ m->dump_size = vma_dump_size(vma, cprm->memory_types);
m->pgoff = vma->vm_pgoff;
m->file = vma->vm_file;
if (m->file)
diff --git a/include/linux/coredump.h b/include/linux/coredump.h
index b252bb2843b33..74af57b9406b8 100644
--- a/include/linux/coredump.h
+++ b/include/linux/coredump.h
@@ -32,8 +32,8 @@ struct coredump_params {
const kernel_siginfo_t *siginfo;
struct file *file;
unsigned long limit;
- /* MMF_DUMP_FILTER_* bits, snapshot of mm->flags at dump start. */
- unsigned long mm_flags;
+ /* COREDUMP_MEMORY_* types to dump, the task's or the server's. */
+ u64 memory_types;
/* Snapshot of dumpable at dump start. */
enum task_dumpable dumpable;
int cpu;
diff --git a/include/uapi/linux/coredump.h b/include/uapi/linux/coredump.h
index f3771861ca485..6d0c53b534ea8 100644
--- a/include/uapi/linux/coredump.h
+++ b/include/uapi/linux/coredump.h
@@ -16,6 +16,9 @@
* requires COREDUMP_KERNEL
* @COREDUMP_SPARSE: describe the holes in the coredump as zero records
* instead of transferring them; requires COREDUMP_RECORDS
+ * @COREDUMP_MEMORY_TYPES: dump the memory types in
+ * coredump_ack->memory_types instead of the ones
+ * the task selected; requires COREDUMP_KERNEL
*/
enum {
COREDUMP_KERNEL = (1ULL << 0),
@@ -24,6 +27,37 @@ enum {
COREDUMP_WAIT = (1ULL << 3),
COREDUMP_RECORDS = (1ULL << 4),
COREDUMP_SPARSE = (1ULL << 5),
+ COREDUMP_MEMORY_TYPES = (1ULL << 6),
+};
+
+/**
+ * coredump memory types
+ * @COREDUMP_MEMORY_ANON_PRIVATE: anonymous private memory
+ * @COREDUMP_MEMORY_ANON_SHARED: anonymous shared memory
+ * @COREDUMP_MEMORY_FILE_PRIVATE: file-backed private memory
+ * @COREDUMP_MEMORY_FILE_SHARED: file-backed shared memory
+ * @COREDUMP_MEMORY_ELF_HEADERS: the first page of a file-backed private
+ * mapping that starts an ELF file
+ * @COREDUMP_MEMORY_HUGETLB_PRIVATE: hugetlb private memory
+ * @COREDUMP_MEMORY_HUGETLB_SHARED: hugetlb shared memory
+ * @COREDUMP_MEMORY_DAX_PRIVATE: DAX private memory
+ * @COREDUMP_MEMORY_DAX_SHARED: DAX shared memory
+ *
+ * A bitmask of memory types a coredump may request to be included. New
+ * memory type bits must ensure that they do not steal memory from an
+ * existing one so a coredump server will continue to get the same
+ * coredumps even if a new bit is introduced.
+ */
+enum {
+ COREDUMP_MEMORY_ANON_PRIVATE = (1ULL << 0),
+ COREDUMP_MEMORY_ANON_SHARED = (1ULL << 1),
+ COREDUMP_MEMORY_FILE_PRIVATE = (1ULL << 2),
+ COREDUMP_MEMORY_FILE_SHARED = (1ULL << 3),
+ COREDUMP_MEMORY_ELF_HEADERS = (1ULL << 4),
+ COREDUMP_MEMORY_HUGETLB_PRIVATE = (1ULL << 5),
+ COREDUMP_MEMORY_HUGETLB_SHARED = (1ULL << 6),
+ COREDUMP_MEMORY_DAX_PRIVATE = (1ULL << 7),
+ COREDUMP_MEMORY_DAX_SHARED = (1ULL << 8),
};
/**
@@ -31,6 +65,8 @@ enum {
* @size: size of struct coredump_req
* @size_ack: known size of struct coredump_ack on this kernel
* @mask: supported features
+ * @memory_types: the memory types the task selected
+ * @memory_types_mask: the memory types this kernel knows
*
* When a coredump happens the kernel will connect to the coredump
* socket and send a coredump request to the coredump server. The @size
@@ -49,15 +85,27 @@ enum {
* struct coredump_ack the kernel knows. Userspace may only send up to
* coredump_req->size_ack bytes to the kernel and must set
* coredump_ack->size accordingly.
+ *
+ * @memory_types is set to the default memory types that are included in
+ * the coredump. This can be overridden by raising bits in
+ * coredump_ack->memory_types.
+ *
+ * @memory_types_mask contains a bitmask of all memory types the kernel
+ * knows about. A coredump server may only raise bits in
+ * coredump_ack->memory_types that are raised in
+ * coredump_req->memory_types_mask.
*/
struct coredump_req {
__u32 size;
__u32 size_ack;
__u64 mask;
+ __u64 memory_types;
+ __u64 memory_types_mask;
};
enum {
COREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */
+ COREDUMP_REQ_SIZE_VER1 = 32U, /* memory_types and memory_types_mask added */
};
/**
@@ -65,6 +113,8 @@ enum {
* @size: size of the struct
* @spare: unused
* @mask: features kernel is supposed to use
+ * @memory_types: memory types to dump, only with COREDUMP_MEMORY_TYPES
+ * in @mask
*
* The @size member must be set to the size of struct coredump_ack. It
* may never exceed what the kernel returned in coredump_req->size_ack
@@ -74,15 +124,30 @@ enum {
* The @mask member must be set to the features the coredump server
* wants the kernel to use. Only bits the kernel returned in
* coredump_req->mask may be set.
+ *
+ * If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the
+ * memory types set in the @memory_types mask. Zero is valid and dumps
+ * no memory apart from the mappings that are always dumped.
+ *
+ * Note that memory a task excluded via MADV_DONTDUMP is always left
+ * out. A coredump server wanting to add or drop memory types instead of
+ * outright replacing it should simply copy coredump_req->memory_types
+ * and then mask off or raise types as needed.
+ *
+ * Note that @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't
+ * raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of
+ * at least COREDUMP_ACK_SIZE_VER1 bytes.
*/
struct coredump_ack {
__u32 size;
__u32 spare;
__u64 mask;
+ __u64 memory_types;
};
enum {
COREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */
+ COREDUMP_ACK_SIZE_VER1 = 24U, /* memory_types added */
};
/**
@@ -90,11 +155,12 @@ enum {
*
* The kernel will place a single byte on the coredump socket. The
* markers notify userspace whether the coredump ack succeeded or
- * failed.
+ * failed. After any marker other than COREDUMP_MARK_REQACK the kernel
+ * closes the connection and no coredump is generated.
*
* @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small
* @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big
- * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid
+ * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask or memory types were invalid
* @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options
* @COREDUMP_MARK_REQACK: the coredump request and ack was successful
* @__COREDUMP_MARK_MAX: the maximum coredump mark value
diff --git a/tools/include/uapi/linux/coredump.h b/tools/include/uapi/linux/coredump.h
index f3771861ca485..6d0c53b534ea8 100644
--- a/tools/include/uapi/linux/coredump.h
+++ b/tools/include/uapi/linux/coredump.h
@@ -16,6 +16,9 @@
* requires COREDUMP_KERNEL
* @COREDUMP_SPARSE: describe the holes in the coredump as zero records
* instead of transferring them; requires COREDUMP_RECORDS
+ * @COREDUMP_MEMORY_TYPES: dump the memory types in
+ * coredump_ack->memory_types instead of the ones
+ * the task selected; requires COREDUMP_KERNEL
*/
enum {
COREDUMP_KERNEL = (1ULL << 0),
@@ -24,6 +27,37 @@ enum {
COREDUMP_WAIT = (1ULL << 3),
COREDUMP_RECORDS = (1ULL << 4),
COREDUMP_SPARSE = (1ULL << 5),
+ COREDUMP_MEMORY_TYPES = (1ULL << 6),
+};
+
+/**
+ * coredump memory types
+ * @COREDUMP_MEMORY_ANON_PRIVATE: anonymous private memory
+ * @COREDUMP_MEMORY_ANON_SHARED: anonymous shared memory
+ * @COREDUMP_MEMORY_FILE_PRIVATE: file-backed private memory
+ * @COREDUMP_MEMORY_FILE_SHARED: file-backed shared memory
+ * @COREDUMP_MEMORY_ELF_HEADERS: the first page of a file-backed private
+ * mapping that starts an ELF file
+ * @COREDUMP_MEMORY_HUGETLB_PRIVATE: hugetlb private memory
+ * @COREDUMP_MEMORY_HUGETLB_SHARED: hugetlb shared memory
+ * @COREDUMP_MEMORY_DAX_PRIVATE: DAX private memory
+ * @COREDUMP_MEMORY_DAX_SHARED: DAX shared memory
+ *
+ * A bitmask of memory types a coredump may request to be included. New
+ * memory type bits must ensure that they do not steal memory from an
+ * existing one so a coredump server will continue to get the same
+ * coredumps even if a new bit is introduced.
+ */
+enum {
+ COREDUMP_MEMORY_ANON_PRIVATE = (1ULL << 0),
+ COREDUMP_MEMORY_ANON_SHARED = (1ULL << 1),
+ COREDUMP_MEMORY_FILE_PRIVATE = (1ULL << 2),
+ COREDUMP_MEMORY_FILE_SHARED = (1ULL << 3),
+ COREDUMP_MEMORY_ELF_HEADERS = (1ULL << 4),
+ COREDUMP_MEMORY_HUGETLB_PRIVATE = (1ULL << 5),
+ COREDUMP_MEMORY_HUGETLB_SHARED = (1ULL << 6),
+ COREDUMP_MEMORY_DAX_PRIVATE = (1ULL << 7),
+ COREDUMP_MEMORY_DAX_SHARED = (1ULL << 8),
};
/**
@@ -31,6 +65,8 @@ enum {
* @size: size of struct coredump_req
* @size_ack: known size of struct coredump_ack on this kernel
* @mask: supported features
+ * @memory_types: the memory types the task selected
+ * @memory_types_mask: the memory types this kernel knows
*
* When a coredump happens the kernel will connect to the coredump
* socket and send a coredump request to the coredump server. The @size
@@ -49,15 +85,27 @@ enum {
* struct coredump_ack the kernel knows. Userspace may only send up to
* coredump_req->size_ack bytes to the kernel and must set
* coredump_ack->size accordingly.
+ *
+ * @memory_types is set to the default memory types that are included in
+ * the coredump. This can be overridden by raising bits in
+ * coredump_ack->memory_types.
+ *
+ * @memory_types_mask contains a bitmask of all memory types the kernel
+ * knows about. A coredump server may only raise bits in
+ * coredump_ack->memory_types that are raised in
+ * coredump_req->memory_types_mask.
*/
struct coredump_req {
__u32 size;
__u32 size_ack;
__u64 mask;
+ __u64 memory_types;
+ __u64 memory_types_mask;
};
enum {
COREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */
+ COREDUMP_REQ_SIZE_VER1 = 32U, /* memory_types and memory_types_mask added */
};
/**
@@ -65,6 +113,8 @@ enum {
* @size: size of the struct
* @spare: unused
* @mask: features kernel is supposed to use
+ * @memory_types: memory types to dump, only with COREDUMP_MEMORY_TYPES
+ * in @mask
*
* The @size member must be set to the size of struct coredump_ack. It
* may never exceed what the kernel returned in coredump_req->size_ack
@@ -74,15 +124,30 @@ enum {
* The @mask member must be set to the features the coredump server
* wants the kernel to use. Only bits the kernel returned in
* coredump_req->mask may be set.
+ *
+ * If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the
+ * memory types set in the @memory_types mask. Zero is valid and dumps
+ * no memory apart from the mappings that are always dumped.
+ *
+ * Note that memory a task excluded via MADV_DONTDUMP is always left
+ * out. A coredump server wanting to add or drop memory types instead of
+ * outright replacing it should simply copy coredump_req->memory_types
+ * and then mask off or raise types as needed.
+ *
+ * Note that @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't
+ * raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of
+ * at least COREDUMP_ACK_SIZE_VER1 bytes.
*/
struct coredump_ack {
__u32 size;
__u32 spare;
__u64 mask;
+ __u64 memory_types;
};
enum {
COREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */
+ COREDUMP_ACK_SIZE_VER1 = 24U, /* memory_types added */
};
/**
@@ -90,11 +155,12 @@ enum {
*
* The kernel will place a single byte on the coredump socket. The
* markers notify userspace whether the coredump ack succeeded or
- * failed.
+ * failed. After any marker other than COREDUMP_MARK_REQACK the kernel
+ * closes the connection and no coredump is generated.
*
* @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small
* @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big
- * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid
+ * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask or memory types were invalid
* @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options
* @COREDUMP_MARK_REQACK: the coredump request and ack was successful
* @__COREDUMP_MARK_MAX: the maximum coredump mark value
diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
index daff908232a22..f5c9bad875463 100644
--- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
+++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
@@ -508,91 +508,89 @@ TEST_F(coredump, socket_request_reject)
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-TEST_F(coredump, socket_request_invalid_flag_combination)
+/* An ack the kernel must refuse and how. */
+struct refused_ack {
+ /* The ack, and how many bytes of it the server sends before it hangs up. */
+ struct coredump_ack ack;
+ size_t bytes;
+ /* The marker the kernel answers with, or none if @no_marker. */
+ enum coredump_mark mark;
+ bool no_marker;
+};
+
+/* Send @refused, expect the kernel to refuse it and hang up. */
+static void check_refused_ack(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(coredump) *self,
+ const struct refused_ack *refused)
{
- int pidfd, ret, status;
+ int pidfd, status;
pid_t pid, pid_coredump_server;
struct pidfd_info info = {};
int ipc_sockets[2];
char c;
+ ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
- ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
- ASSERT_EQ(ret, 0);
-
pid_coredump_server = fork();
ASSERT_GE(pid_coredump_server, 0);
if (pid_coredump_server == 0) {
- struct coredump_req req = {};
int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
int exit_code = EXIT_FAILURE;
+ struct coredump_req req = {};
close(ipc_sockets[0]);
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0) {
- fprintf(stderr, "socket_request_invalid_flag_combination: create_and_listen_unix_socket failed: %m\n");
+ if (fd_server < 0)
goto out;
- }
- if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_request_invalid_flag_combination: write_nointr to ipc socket failed: %m\n");
+ if (write_nointr(ipc_sockets[1], "1", 1) < 0)
goto out;
- }
close(ipc_sockets[1]);
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_invalid_flag_combination: accept4 failed: %m\n");
+ if (fd_coredump < 0)
goto out;
- }
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_request_invalid_flag_combination: get_peer_pidfd failed\n");
+ if (fd_peer_pidfd < 0)
goto out;
- }
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: get_pidfd_info failed\n");
+ /* The task shows as dumping while it waits for the ack. */
+ if (!get_pidfd_info(fd_peer_pidfd, &info))
goto out;
- }
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: PIDFD_INFO_COREDUMP not set in mask\n");
+ if (!(info.mask & PIDFD_INFO_COREDUMP) ||
+ !(info.coredump_mask & PIDFD_COREDUMPED)) {
+ fprintf(stderr, "Peer isn't marked as dumping\n");
goto out;
}
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: PIDFD_COREDUMPED not set in coredump_mask\n");
+ if (!read_coredump_req(fd_coredump, &req))
goto out;
- }
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: read_coredump_req failed\n");
+ if (!check_coredump_req(&req))
goto out;
- }
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: check_coredump_req failed\n");
+ if (!send_coredump_ack_bytes(fd_coredump, &refused->ack,
+ refused->bytes))
goto out;
- }
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_KERNEL | COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: send_coredump_ack failed\n");
+ /* Nothing more to say. A server that died looks the same. */
+ if (shutdown(fd_coredump, SHUT_WR))
goto out;
- }
- if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING)) {
- fprintf(stderr, "socket_request_invalid_flag_combination: read_marker COREDUMP_MARK_CONFLICTING failed\n");
+ if (!refused->no_marker &&
+ !read_marker(fd_coredump, refused->mark))
+ goto out;
+
+ /* The kernel hangs up after a refusal, marker or not. */
+ if (!read_hangup(fd_coredump))
goto out;
- }
exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_invalid_flag_combination: completed successfully\n");
out:
if (fd_peer_pidfd >= 0)
close(fd_peer_pidfd);
@@ -627,7 +625,83 @@ TEST_F(coredump, socket_request_invalid_flag_combination)
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
+/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */
+static void check_conflicting_ack(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(coredump) *self, __u64 ack_mask)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = ack_mask,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_CONFLICTING,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* More than one of KERNEL, USERSPACE and REJECT. */
+TEST_F(coredump, socket_request_invalid_flag_combination)
+{
+ check_conflicting_ack(_metadata, self,
+ COREDUMP_KERNEL | COREDUMP_REJECT | COREDUMP_WAIT);
+}
+
+/* A flag the kernel didn't advertise in coredump_req->mask. */
TEST_F(coredump, socket_request_unknown_flag)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = 1ULL << 63,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_UNSUPPORTED,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* An ack smaller than the first published struct. */
+TEST_F(coredump, socket_request_invalid_size_small)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = COREDUMP_ACK_SIZE_VER0 / 2,
+ .mask = COREDUMP_REJECT | COREDUMP_WAIT,
+ },
+ .bytes = COREDUMP_ACK_SIZE_VER0 / 2,
+ .mark = COREDUMP_MARK_MINSIZE,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* An ack bigger than the kernel said it accepts. */
+TEST_F(coredump, socket_request_invalid_size_large)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE,
+ .mask = COREDUMP_REJECT | COREDUMP_WAIT,
+ },
+ .bytes = COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE,
+ .mark = COREDUMP_MARK_MAXSIZE,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/*
+ * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGSEGV
+ *
+ * Verify that when using socket-based coredump protocol,
+ * the coredump_signal field is correctly exposed as SIGSEGV.
+ * Also check that the coredump_code field is correctly exposed
+ * as SEGV_MAPERR.
+ */
+TEST_F(coredump, socket_coredump_signal_sigsegv)
{
int pidfd, ret, status;
pid_t pid, pid_coredump_server;
@@ -651,12 +725,12 @@ TEST_F(coredump, socket_request_unknown_flag)
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
if (fd_server < 0) {
- fprintf(stderr, "socket_request_unknown_flag: create_and_listen_unix_socket failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: create_and_listen_unix_socket failed: %m\n");
goto out;
}
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_request_unknown_flag: write_nointr to ipc socket failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: write_nointr to ipc socket failed: %m\n");
goto out;
}
@@ -664,53 +738,73 @@ TEST_F(coredump, socket_request_unknown_flag)
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_unknown_flag: accept4 failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: accept4 failed: %m\n");
goto out;
}
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_request_unknown_flag: get_peer_pidfd failed\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: get_peer_pidfd failed\n");
goto out;
}
if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_unknown_flag: get_pidfd_info failed\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: get_pidfd_info failed\n");
goto out;
}
if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_request_unknown_flag: PIDFD_INFO_COREDUMP not set in mask\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP not set in mask\n");
goto out;
}
if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_unknown_flag: PIDFD_COREDUMPED not set in coredump_mask\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_COREDUMPED not set in coredump_mask\n");
goto out;
}
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_unknown_flag: read_coredump_req failed\n");
+ /* Verify coredump_signal is available and correct */
+ if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
goto out;
}
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_unknown_flag: check_coredump_req failed\n");
+ if (info.coredump_signal != SIGSEGV) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_signal=%d, expected SIGSEGV=%d\n",
+ info.coredump_signal, SIGSEGV);
+ goto out;
+ }
+
+ /* Verify coredump_code is available and correct */
+ if (!(info.mask & PIDFD_INFO_COREDUMP_CODE)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_CODE not set in mask\n");
+ goto out;
+ }
+
+ if (info.coredump_code != SEGV_MAPERR) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_code=%d, expected SEGV_MAPERR=%d\n",
+ info.coredump_code, SEGV_MAPERR);
goto out;
}
- if (!send_coredump_ack(fd_coredump, &req, (1ULL << 63), 0)) {
- fprintf(stderr, "socket_request_unknown_flag: send_coredump_ack failed\n");
+ if (!read_coredump_req(fd_coredump, &req)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: read_coredump_req failed\n");
+ goto out;
+ }
+
+ if (!send_coredump_ack(fd_coredump, &req,
+ COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: send_coredump_ack failed\n");
goto out;
}
- if (!read_marker(fd_coredump, COREDUMP_MARK_UNSUPPORTED)) {
- fprintf(stderr, "socket_request_unknown_flag: read_marker COREDUMP_MARK_UNSUPPORTED failed\n");
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
+ fprintf(stderr, "socket_coredump_signal_sigsegv: read_marker COREDUMP_MARK_REQACK failed\n");
goto out;
}
exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_unknown_flag: completed successfully\n");
+ fprintf(stderr, "socket_coredump_signal_sigsegv: completed successfully\n");
out:
if (fd_peer_pidfd >= 0)
close(fd_peer_pidfd);
@@ -736,16 +830,27 @@ TEST_F(coredump, socket_request_unknown_flag)
waitpid(pid, &status, 0);
ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
+ ASSERT_EQ(WTERMSIG(status), SIGSEGV);
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
+ ASSERT_EQ(info.coredump_signal, SIGSEGV);
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_CODE));
+ ASSERT_EQ(info.coredump_code, SEGV_MAPERR);
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-TEST_F(coredump, socket_request_invalid_size_small)
+/*
+ * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGABRT
+ *
+ * Verify that when using socket-based coredump protocol,
+ * the coredump_signal field is correctly exposed as SIGABRT.
+ * Also check that the coredump_code field is correctly exposed
+ * as SI_TKILL.
+ */
+TEST_F(coredump, socket_coredump_signal_sigabrt)
{
int pidfd, ret, status;
pid_t pid, pid_coredump_server;
@@ -769,12 +874,12 @@ TEST_F(coredump, socket_request_invalid_size_small)
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
if (fd_server < 0) {
- fprintf(stderr, "socket_request_invalid_size_small: create_and_listen_unix_socket failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: create_and_listen_unix_socket failed: %m\n");
goto out;
}
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_request_invalid_size_small: write_nointr to ipc socket failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: write_nointr to ipc socket failed: %m\n");
goto out;
}
@@ -782,55 +887,67 @@ TEST_F(coredump, socket_request_invalid_size_small)
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_invalid_size_small: accept4 failed: %m\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: accept4 failed: %m\n");
goto out;
}
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_request_invalid_size_small: get_peer_pidfd failed\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: get_peer_pidfd failed\n");
goto out;
}
if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_invalid_size_small: get_pidfd_info failed\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: get_pidfd_info failed\n");
goto out;
}
if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_request_invalid_size_small: PIDFD_INFO_COREDUMP not set in mask\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP not set in mask\n");
goto out;
}
if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_invalid_size_small: PIDFD_COREDUMPED not set in coredump_mask\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_COREDUMPED not set in coredump_mask\n");
goto out;
}
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_invalid_size_small: read_coredump_req failed\n");
+ /* Verify coredump_signal is available and correct */
+ if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
goto out;
}
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_invalid_size_small: check_coredump_req failed\n");
+ if (info.coredump_signal != SIGABRT) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_signal=%d, expected SIGABRT=%d\n",
+ info.coredump_signal, SIGABRT);
+ goto out;
+ }
+
+ if (info.coredump_code != SI_TKILL) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_code=%d, expected SI_TKILL=%d\n",
+ info.coredump_code, SI_TKILL);
+ goto out;
+ }
+
+ if (!read_coredump_req(fd_coredump, &req)) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: read_coredump_req failed\n");
goto out;
}
if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_REJECT | COREDUMP_WAIT,
- COREDUMP_ACK_SIZE_VER0 / 2)) {
- fprintf(stderr, "socket_request_invalid_size_small: send_coredump_ack failed\n");
+ COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: send_coredump_ack failed\n");
goto out;
}
- if (!read_marker(fd_coredump, COREDUMP_MARK_MINSIZE)) {
- fprintf(stderr, "socket_request_invalid_size_small: read_marker COREDUMP_MARK_MINSIZE failed\n");
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
+ fprintf(stderr, "socket_coredump_signal_sigabrt: read_marker COREDUMP_MARK_REQACK failed\n");
goto out;
}
exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_invalid_size_small: completed successfully\n");
+ fprintf(stderr, "socket_coredump_signal_sigabrt: completed successfully\n");
out:
if (fd_peer_pidfd >= 0)
close(fd_peer_pidfd);
@@ -849,513 +966,104 @@ TEST_F(coredump, socket_request_invalid_size_small)
pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0)
- crashing_child();
+ abort();
pidfd = sys_pidfd_open(pid, 0);
ASSERT_GE(pidfd, 0);
waitpid(pid, &status, 0);
ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
+ ASSERT_EQ(WTERMSIG(status), SIGABRT);
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
+ ASSERT_EQ(info.coredump_signal, SIGABRT);
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_CODE));
+ ASSERT_EQ(info.coredump_code, SI_TKILL);
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-TEST_F(coredump, socket_request_invalid_size_large)
+TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps, 500)
{
- int pidfd, ret, status;
- pid_t pid, pid_coredump_server;
+ int pidfd[NUM_CRASHING_COREDUMPS], status[NUM_CRASHING_COREDUMPS];
+ pid_t pid[NUM_CRASHING_COREDUMPS], pid_coredump_server;
struct pidfd_info info = {};
int ipc_sockets[2];
char c;
ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
- ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
- ASSERT_EQ(ret, 0);
+ ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
pid_coredump_server = fork();
ASSERT_GE(pid_coredump_server, 0);
if (pid_coredump_server == 0) {
- struct coredump_req req = {};
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
+ int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;
int exit_code = EXIT_FAILURE;
+ struct coredump_req req = {};
close(ipc_sockets[0]);
-
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
if (fd_server < 0) {
- fprintf(stderr, "socket_request_invalid_size_large: create_and_listen_unix_socket failed: %m\n");
+ fprintf(stderr, "Failed to create and listen on unix socket\n");
goto out;
}
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_request_invalid_size_large: write_nointr to ipc socket failed: %m\n");
+ fprintf(stderr, "Failed to notify parent via ipc socket\n");
goto out;
}
-
close(ipc_sockets[1]);
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_invalid_size_large: accept4 failed: %m\n");
- goto out;
- }
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_request_invalid_size_large: get_peer_pidfd failed\n");
- goto out;
- }
+ for (int i = 0; i < NUM_CRASHING_COREDUMPS; i++) {
+ fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
+ if (fd_coredump < 0) {
+ fprintf(stderr, "accept4 failed: %m\n");
+ goto out;
+ }
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_invalid_size_large: get_pidfd_info failed\n");
- goto out;
- }
+ fd_peer_pidfd = get_peer_pidfd(fd_coredump);
+ if (fd_peer_pidfd < 0) {
+ fprintf(stderr, "get_peer_pidfd failed for fd %d: %m\n", fd_coredump);
+ goto out;
+ }
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_request_invalid_size_large: PIDFD_INFO_COREDUMP not set in mask\n");
- goto out;
- }
+ if (!get_pidfd_info(fd_peer_pidfd, &info)) {
+ fprintf(stderr, "get_pidfd_info failed for fd %d\n", fd_peer_pidfd);
+ goto out;
+ }
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_invalid_size_large: PIDFD_COREDUMPED not set in coredump_mask\n");
- goto out;
- }
+ if (!(info.mask & PIDFD_INFO_COREDUMP)) {
+ fprintf(stderr, "pidfd info missing PIDFD_INFO_COREDUMP for fd %d\n", fd_peer_pidfd);
+ goto out;
+ }
+ if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
+ fprintf(stderr, "pidfd info missing PIDFD_COREDUMPED for fd %d\n", fd_peer_pidfd);
+ goto out;
+ }
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_invalid_size_large: read_coredump_req failed\n");
- goto out;
- }
+ if (!read_coredump_req(fd_coredump, &req)) {
+ fprintf(stderr, "read_coredump_req failed for fd %d\n", fd_coredump);
+ goto out;
+ }
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_invalid_size_large: check_coredump_req failed\n");
- goto out;
- }
+ if (!check_coredump_req(&req)) {
+ fprintf(stderr, "check_coredump_req failed for fd %d\n", fd_coredump);
+ goto out;
+ }
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_REJECT | COREDUMP_WAIT,
- COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE)) {
- fprintf(stderr, "socket_request_invalid_size_large: send_coredump_ack failed\n");
- goto out;
- }
+ if (!send_coredump_ack(fd_coredump, &req,
+ COREDUMP_KERNEL | COREDUMP_WAIT, 0)) {
+ fprintf(stderr, "send_coredump_ack failed for fd %d\n", fd_coredump);
+ goto out;
+ }
- if (!read_marker(fd_coredump, COREDUMP_MARK_MAXSIZE)) {
- fprintf(stderr, "socket_request_invalid_size_large: read_marker COREDUMP_MARK_MAXSIZE failed\n");
- goto out;
- }
-
- exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_invalid_size_large: completed successfully\n");
-out:
- if (fd_peer_pidfd >= 0)
- close(fd_peer_pidfd);
- if (fd_coredump >= 0)
- close(fd_coredump);
- if (fd_server >= 0)
- close(fd_server);
- _exit(exit_code);
- }
- self->pid_coredump_server = pid_coredump_server;
-
- EXPECT_EQ(close(ipc_sockets[1]), 0);
- ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
- EXPECT_EQ(close(ipc_sockets[0]), 0);
-
- pid = fork();
- ASSERT_GE(pid, 0);
- if (pid == 0)
- crashing_child();
-
- pidfd = sys_pidfd_open(pid, 0);
- ASSERT_GE(pidfd, 0);
-
- waitpid(pid, &status, 0);
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
-
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
-
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
-}
-
-/*
- * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGSEGV
- *
- * Verify that when using socket-based coredump protocol,
- * the coredump_signal field is correctly exposed as SIGSEGV.
- * Also check that the coredump_code field is correctly exposed
- * as SEGV_MAPERR.
- */
-TEST_F(coredump, socket_coredump_signal_sigsegv)
-{
- int pidfd, ret, status;
- pid_t pid, pid_coredump_server;
- struct pidfd_info info = {};
- int ipc_sockets[2];
- char c;
-
- ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
-
- ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
- ASSERT_EQ(ret, 0);
-
- pid_coredump_server = fork();
- ASSERT_GE(pid_coredump_server, 0);
- if (pid_coredump_server == 0) {
- struct coredump_req req = {};
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
- int exit_code = EXIT_FAILURE;
-
- close(ipc_sockets[0]);
-
- fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: create_and_listen_unix_socket failed: %m\n");
- goto out;
- }
-
- if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: write_nointr to ipc socket failed: %m\n");
- goto out;
- }
-
- close(ipc_sockets[1]);
-
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: accept4 failed: %m\n");
- goto out;
- }
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: get_peer_pidfd failed\n");
- goto out;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: get_pidfd_info failed\n");
- goto out;
- }
-
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP not set in mask\n");
- goto out;
- }
-
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_COREDUMPED not set in coredump_mask\n");
- goto out;
- }
-
- /* Verify coredump_signal is available and correct */
- if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
- goto out;
- }
-
- if (info.coredump_signal != SIGSEGV) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_signal=%d, expected SIGSEGV=%d\n",
- info.coredump_signal, SIGSEGV);
- goto out;
- }
-
- /* Verify coredump_code is available and correct */
- if (!(info.mask & PIDFD_INFO_COREDUMP_CODE)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_CODE not set in mask\n");
- goto out;
- }
-
- if (info.coredump_code != SEGV_MAPERR) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_code=%d, expected SEGV_MAPERR=%d\n",
- info.coredump_code, SEGV_MAPERR);
- goto out;
- }
-
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: read_coredump_req failed\n");
- goto out;
- }
-
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: send_coredump_ack failed\n");
- goto out;
- }
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
- fprintf(stderr, "socket_coredump_signal_sigsegv: read_marker COREDUMP_MARK_REQACK failed\n");
- goto out;
- }
-
- exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_coredump_signal_sigsegv: completed successfully\n");
-out:
- if (fd_peer_pidfd >= 0)
- close(fd_peer_pidfd);
- if (fd_coredump >= 0)
- close(fd_coredump);
- if (fd_server >= 0)
- close(fd_server);
- _exit(exit_code);
- }
- self->pid_coredump_server = pid_coredump_server;
-
- EXPECT_EQ(close(ipc_sockets[1]), 0);
- ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
- EXPECT_EQ(close(ipc_sockets[0]), 0);
-
- pid = fork();
- ASSERT_GE(pid, 0);
- if (pid == 0)
- crashing_child();
-
- pidfd = sys_pidfd_open(pid, 0);
- ASSERT_GE(pidfd, 0);
-
- waitpid(pid, &status, 0);
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_EQ(WTERMSIG(status), SIGSEGV);
-
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
- ASSERT_EQ(info.coredump_signal, SIGSEGV);
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_CODE));
- ASSERT_EQ(info.coredump_code, SEGV_MAPERR);
-
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
-}
-
-/*
- * Test: PIDFD_INFO_COREDUMP_SIGNAL via socket coredump with SIGABRT
- *
- * Verify that when using socket-based coredump protocol,
- * the coredump_signal field is correctly exposed as SIGABRT.
- * Also check that the coredump_code field is correctly exposed
- * as SI_TKILL.
- */
-TEST_F(coredump, socket_coredump_signal_sigabrt)
-{
- int pidfd, ret, status;
- pid_t pid, pid_coredump_server;
- struct pidfd_info info = {};
- int ipc_sockets[2];
- char c;
-
- ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
-
- ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
- ASSERT_EQ(ret, 0);
-
- pid_coredump_server = fork();
- ASSERT_GE(pid_coredump_server, 0);
- if (pid_coredump_server == 0) {
- struct coredump_req req = {};
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
- int exit_code = EXIT_FAILURE;
-
- close(ipc_sockets[0]);
-
- fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: create_and_listen_unix_socket failed: %m\n");
- goto out;
- }
-
- if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: write_nointr to ipc socket failed: %m\n");
- goto out;
- }
-
- close(ipc_sockets[1]);
-
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: accept4 failed: %m\n");
- goto out;
- }
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: get_peer_pidfd failed\n");
- goto out;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: get_pidfd_info failed\n");
- goto out;
- }
-
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP not set in mask\n");
- goto out;
- }
-
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_COREDUMPED not set in coredump_mask\n");
- goto out;
- }
-
- /* Verify coredump_signal is available and correct */
- if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
- goto out;
- }
-
- if (info.coredump_signal != SIGABRT) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_signal=%d, expected SIGABRT=%d\n",
- info.coredump_signal, SIGABRT);
- goto out;
- }
-
- if (info.coredump_code != SI_TKILL) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_code=%d, expected SI_TKILL=%d\n",
- info.coredump_code, SI_TKILL);
- goto out;
- }
-
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: read_coredump_req failed\n");
- goto out;
- }
-
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_REJECT | COREDUMP_WAIT, 0)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: send_coredump_ack failed\n");
- goto out;
- }
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
- fprintf(stderr, "socket_coredump_signal_sigabrt: read_marker COREDUMP_MARK_REQACK failed\n");
- goto out;
- }
-
- exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_coredump_signal_sigabrt: completed successfully\n");
-out:
- if (fd_peer_pidfd >= 0)
- close(fd_peer_pidfd);
- if (fd_coredump >= 0)
- close(fd_coredump);
- if (fd_server >= 0)
- close(fd_server);
- _exit(exit_code);
- }
- self->pid_coredump_server = pid_coredump_server;
-
- EXPECT_EQ(close(ipc_sockets[1]), 0);
- ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
- EXPECT_EQ(close(ipc_sockets[0]), 0);
-
- pid = fork();
- ASSERT_GE(pid, 0);
- if (pid == 0)
- abort();
-
- pidfd = sys_pidfd_open(pid, 0);
- ASSERT_GE(pidfd, 0);
-
- waitpid(pid, &status, 0);
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_EQ(WTERMSIG(status), SIGABRT);
-
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
- ASSERT_EQ(info.coredump_signal, SIGABRT);
- ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_CODE));
- ASSERT_EQ(info.coredump_code, SI_TKILL);
-
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
-}
-
-TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps, 500)
-{
- int pidfd[NUM_CRASHING_COREDUMPS], status[NUM_CRASHING_COREDUMPS];
- pid_t pid[NUM_CRASHING_COREDUMPS], pid_coredump_server;
- struct pidfd_info info = {};
- int ipc_sockets[2];
- char c;
-
- ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
-
- ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
-
- pid_coredump_server = fork();
- ASSERT_GE(pid_coredump_server, 0);
- if (pid_coredump_server == 0) {
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;
- int exit_code = EXIT_FAILURE;
- struct coredump_req req = {};
-
- close(ipc_sockets[0]);
- fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0) {
- fprintf(stderr, "Failed to create and listen on unix socket\n");
- goto out;
- }
-
- if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
- fprintf(stderr, "Failed to notify parent via ipc socket\n");
- goto out;
- }
- close(ipc_sockets[1]);
-
- for (int i = 0; i < NUM_CRASHING_COREDUMPS; i++) {
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "accept4 failed: %m\n");
- goto out;
- }
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0) {
- fprintf(stderr, "get_peer_pidfd failed for fd %d: %m\n", fd_coredump);
- goto out;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "get_pidfd_info failed for fd %d\n", fd_peer_pidfd);
- goto out;
- }
-
- if (!(info.mask & PIDFD_INFO_COREDUMP)) {
- fprintf(stderr, "pidfd info missing PIDFD_INFO_COREDUMP for fd %d\n", fd_peer_pidfd);
- goto out;
- }
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "pidfd info missing PIDFD_COREDUMPED for fd %d\n", fd_peer_pidfd);
- goto out;
- }
-
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "read_coredump_req failed for fd %d\n", fd_coredump);
- goto out;
- }
-
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "check_coredump_req failed for fd %d\n", fd_coredump);
- goto out;
- }
-
- if (!send_coredump_ack(fd_coredump, &req,
- COREDUMP_KERNEL | COREDUMP_WAIT, 0)) {
- fprintf(stderr, "send_coredump_ack failed for fd %d\n", fd_coredump);
- goto out;
- }
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
- fprintf(stderr, "read_marker failed for fd %d\n", fd_coredump);
- goto out;
- }
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) {
+ fprintf(stderr, "read_marker failed for fd %d\n", fd_coredump);
+ goto out;
+ }
fd_core_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
if (fd_core_file < 0) {
@@ -2015,113 +1723,27 @@ TEST_F(coredump, socket_request_sparse_blob_upload)
ASSERT_EQ(read_nointr(pipefds[0], &received, sizeof(received)),
sizeof(received));
ASSERT_EQ(read_nointr(pipefds[0], &coredump_size, sizeof(coredump_size)),
- sizeof(coredump_size));
- EXPECT_EQ(close(pipefds[0]), 0);
-
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
-
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
-
- /* The mapping is in the coredump, holes included. */
- ASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE);
-
- /* The object isn't sparse and doesn't carry them. */
- ASSERT_EQ(stat("/tmp/coredump.file", &st), 0);
- ASSERT_LT(st.st_size, coredump_size / 8);
-
- /* And a debugger still sees an ordinary ELF core file. */
- fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC);
- ASSERT_GE(fd_core_file, 0);
- ASSERT_TRUE(is_elf_core(fd_core_file));
- EXPECT_EQ(close(fd_core_file), 0);
-}
-
-/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */
-static void check_conflicting_ack(struct __test_metadata *const _metadata,
- FIXTURE_DATA(coredump) *self, __u64 ack_mask)
-{
- int pidfd, status;
- pid_t pid, pid_coredump_server;
- struct pidfd_info info = {};
- int ipc_sockets[2];
- char c;
-
- ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
- ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
-
- pid_coredump_server = fork();
- ASSERT_GE(pid_coredump_server, 0);
- if (pid_coredump_server == 0) {
- int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
- int exit_code = EXIT_FAILURE;
- struct coredump_req req = {};
-
- close(ipc_sockets[0]);
-
- fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
- if (fd_server < 0)
- goto out;
-
- if (write_nointr(ipc_sockets[1], "1", 1) < 0)
- goto out;
-
- close(ipc_sockets[1]);
-
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0)
- goto out;
-
- fd_peer_pidfd = get_peer_pidfd(fd_coredump);
- if (fd_peer_pidfd < 0)
- goto out;
-
- if (!read_coredump_req(fd_coredump, &req))
- goto out;
-
- if (!check_coredump_req(&req))
- goto out;
-
- if (!send_coredump_ack(fd_coredump, &req, ack_mask, 0))
- goto out;
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING))
- goto out;
-
- exit_code = EXIT_SUCCESS;
-out:
- if (fd_peer_pidfd >= 0)
- close(fd_peer_pidfd);
- if (fd_coredump >= 0)
- close(fd_coredump);
- if (fd_server >= 0)
- close(fd_server);
- _exit(exit_code);
- }
- self->pid_coredump_server = pid_coredump_server;
-
- EXPECT_EQ(close(ipc_sockets[1]), 0);
- ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
- EXPECT_EQ(close(ipc_sockets[0]), 0);
-
- pid = fork();
- ASSERT_GE(pid, 0);
- if (pid == 0)
- crashing_child();
-
- pidfd = sys_pidfd_open(pid, 0);
- ASSERT_GE(pidfd, 0);
-
- waitpid(pid, &status, 0);
- ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
+ sizeof(coredump_size));
+ EXPECT_EQ(close(pipefds[0]), 0);
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
+
+ /* The mapping is in the coredump, holes included. */
+ ASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE);
+
+ /* The object isn't sparse and doesn't carry them. */
+ ASSERT_EQ(stat("/tmp/coredump.file", &st), 0);
+ ASSERT_LT(st.st_size, coredump_size / 8);
+
+ /* And a debugger still sees an ordinary ELF core file. */
+ fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC);
+ ASSERT_GE(fd_core_file, 0);
+ ASSERT_TRUE(is_elf_core(fd_core_file));
+ EXPECT_EQ(close(fd_core_file), 0);
}
/* COREDUMP_RECORDS applies to a coredump the kernel writes, nothing else. */
@@ -2320,4 +1942,674 @@ TEST_F(coredump, socket_request_stream_choice_large)
ASSERT_LT(choice.received, choice.size / 8);
}
+/* What a coredump server was built with. */
+struct server_build {
+ /* sizeof(struct coredump_req) and sizeof(struct coredump_ack) back then. */
+ size_t req_size;
+ size_t ack_size;
+ /* The features it raises if the kernel offers them. */
+ __u64 wants;
+ /* Its policy: what it drops from and adds to the task's selection. */
+ __u64 drop;
+ __u64 add;
+};
+
+/* A server from when the structs were first published: kernel-written dumps. */
+static const struct server_build server_build_ver0 = {
+ .req_size = COREDUMP_REQ_SIZE_VER0,
+ .ack_size = COREDUMP_ACK_SIZE_VER0,
+ .wants = COREDUMP_KERNEL,
+};
+
+/* A server built against this header: no shared memory, always the ELF headers. */
+static const struct server_build server_build_ver1 = {
+ .req_size = sizeof(struct coredump_req),
+ .ack_size = sizeof(struct coredump_ack),
+ .wants = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .drop = COREDUMP_MEMORY_ANON_SHARED | COREDUMP_MEMORY_FILE_SHARED,
+ .add = COREDUMP_MEMORY_ELF_HEADERS,
+};
+
+/*
+ * Build the ack the way a server does: from what the kernel offers, what
+ * this build implements, and what fits in the ack the kernel accepts.
+ * Fields the build never read are zero and never consulted.
+ */
+static void negotiate(const struct coredump_req *req,
+ const struct server_build *build,
+ struct coredump_ack *ack)
+{
+ __u64 offered = req->mask & build->wants;
+
+ memset(ack, 0, sizeof(*ack));
+ ack->size = build->ack_size < req->size_ack ? build->ack_size : req->size_ack;
+ /* These builds only ever have the kernel write the coredump. */
+ ack->mask = COREDUMP_KERNEL;
+
+ /* Sparse needs records, records need the kernel to write. */
+ if (offered & COREDUMP_RECORDS) {
+ ack->mask |= COREDUMP_RECORDS;
+ if (offered & COREDUMP_SPARSE)
+ ack->mask |= COREDUMP_SPARSE;
+ }
+
+ /* The memory types need an ack that carries them. */
+ if ((offered & COREDUMP_MEMORY_TYPES) && ack->size >= COREDUMP_ACK_SIZE_VER1) {
+ ack->mask |= COREDUMP_MEMORY_TYPES;
+ /* Start from the task's selection; only advertised types pass. */
+ ack->memory_types = (req->memory_types & ~build->drop) | build->add;
+ ack->memory_types &= req->memory_types_mask;
+ }
+}
+
+/* What a memory types test asks of the kernel and what it expects back. */
+struct memory_choice {
+ /* Memory types the crashing child selects, or FILTER_TASK_INHERIT. */
+ __u64 task_filter;
+ /* Negotiate the ack as this server build, NULL to send it as given. */
+ const struct server_build *build;
+ /* The ack, or what the negotiation must arrive at. */
+ __u64 mask;
+ __u64 memory_types;
+ size_t size_ack;
+ /* The shared mapping is in the coredump with all of its memory. */
+ bool shared_dumped;
+ /* No memory at all. Pull a page from /proc/<pid>/mem instead. */
+ bool skeleton;
+};
+
+/* A skeleton still carries the vdso and friends, nothing bigger. */
+#define SKELETON_DATA_PAGES 16
+
+/*
+ * The crashing child maps shared anonymous memory and tells the server
+ * where. The server acks with @choice and checks whether that mapping's
+ * segment in the coredump carries its memory.
+ */
+static void check_memory_dump(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(coredump) *self,
+ const struct memory_choice *choice)
+{
+ int pidfd, status;
+ pid_t pid, pid_coredump_server;
+ struct pidfd_info info = {};
+ int ipc_sockets[2];
+ int addr_pipe[2];
+ char c;
+
+ ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
+ ASSERT_EQ(pipe(addr_pipe), 0);
+ ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
+
+ pid_coredump_server = fork();
+ ASSERT_GE(pid_coredump_server, 0);
+ if (pid_coredump_server == 0) {
+ int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
+ int fd_file = -1;
+ int exit_code = EXIT_FAILURE;
+ struct coredump_req req = {};
+ struct coredump_ack ack = {
+ .size = choice->size_ack,
+ .mask = choice->mask,
+ .memory_types = choice->memory_types,
+ };
+ /* How much of the request this server reads. */
+ size_t req_size = choice->build ? choice->build->req_size : sizeof(req);
+ __u64 task_filter;
+ ElfW(Phdr) segment;
+ ssize_t received;
+ off_t size;
+ char *addr;
+
+ close(ipc_sockets[0]);
+ close(addr_pipe[1]);
+
+ fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
+ if (fd_server < 0)
+ goto out;
+
+ if (write_nointr(ipc_sockets[1], "1", 1) < 0)
+ goto out;
+
+ close(ipc_sockets[1]);
+
+ fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
+ if (fd_coredump < 0)
+ goto out;
+
+ fd_peer_pidfd = get_peer_pidfd(fd_coredump);
+ if (fd_peer_pidfd < 0)
+ goto out;
+
+ fd_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
+ if (fd_file < 0)
+ goto out;
+
+ if (!read_coredump_req_sized(fd_coredump, &req, req_size))
+ goto out;
+
+ if (!peer_coredump_filter(fd_peer_pidfd, &task_filter))
+ goto out;
+
+ /* A build from before the memory types never read that far. */
+ if (req_size >= COREDUMP_REQ_SIZE_VER1) {
+ if (!check_coredump_req(&req))
+ goto out;
+
+ /* The request reports the memory types the task selected. */
+ if (req.memory_types != task_filter) {
+ fprintf(stderr, "Request reports 0x%llx, task selected 0x%llx\n",
+ (unsigned long long)req.memory_types,
+ (unsigned long long)task_filter);
+ goto out;
+ }
+ }
+
+ if (choice->task_filter != FILTER_TASK_INHERIT &&
+ task_filter != choice->task_filter) {
+ fprintf(stderr, "Task selected 0x%llx, child asked for 0x%llx\n",
+ (unsigned long long)task_filter,
+ (unsigned long long)choice->task_filter);
+ goto out;
+ }
+
+ /* The child sent the address of its mapping before it crashed. */
+ if (read_nointr(addr_pipe[0], &addr, sizeof(addr)) != sizeof(addr))
+ goto out;
+
+ /* A server build negotiates its ack and must arrive at the choice. */
+ if (choice->build) {
+ negotiate(&req, choice->build, &ack);
+
+ if (ack.size != choice->size_ack || ack.mask != choice->mask ||
+ ack.memory_types != choice->memory_types) {
+ fprintf(stderr,
+ "Negotiated %u bytes, mask 0x%llx, types 0x%llx\n",
+ ack.size, (unsigned long long)ack.mask,
+ (unsigned long long)ack.memory_types);
+ goto out;
+ }
+ }
+
+ if (!send_coredump_ack_types(fd_coredump, &req, ack.mask,
+ ack.memory_types, ack.size))
+ goto out;
+
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK))
+ goto out;
+
+ if (ack.mask & COREDUMP_RECORDS)
+ received = recv_coredump_records(fd_coredump, fd_file,
+ &size, NULL, -1);
+ else
+ received = recv_coredump_bytes(fd_coredump, fd_file);
+ if (received < 0)
+ goto out;
+
+ if (!is_elf_core(fd_file))
+ goto out;
+
+ /* A dump ending in holes or empty segments must still be whole. */
+ if (!check_coredump_extent(fd_file))
+ goto out;
+
+ if (!find_coredump_segment(fd_file, (__u64)(uintptr_t)addr, &segment))
+ goto out;
+
+ if (segment.p_memsz != MEMORY_MAPPING_SIZE) {
+ fprintf(stderr, "Segment spans %llu bytes, the mapping %u\n",
+ (unsigned long long)segment.p_memsz,
+ MEMORY_MAPPING_SIZE);
+ goto out;
+ }
+
+ if (segment.p_filesz != (choice->shared_dumped ? segment.p_memsz : 0)) {
+ fprintf(stderr, "Segment carries %llu bytes, expected %s of them\n",
+ (unsigned long long)segment.p_filesz,
+ choice->shared_dumped ? "all" : "none");
+ goto out;
+ }
+
+ if (choice->skeleton) {
+ __u64 data, notes, data_max;
+ char buf[PAGE_SIZE];
+
+ if (!sum_coredump_segments(fd_file, &data, ¬es))
+ goto out;
+
+ data_max = SKELETON_DATA_PAGES * sysconf(_SC_PAGESIZE);
+ if (!notes || data > data_max) {
+ fprintf(stderr, "Skeleton has %llu note and %llu memory bytes\n",
+ (unsigned long long)notes,
+ (unsigned long long)data);
+ goto out;
+ }
+
+ /* The task is parked in COREDUMP_WAIT with its memory. */
+ if (peer_read_mem(fd_peer_pidfd, (__u64)(uintptr_t)addr,
+ buf, sizeof(buf)) != sizeof(buf))
+ goto out;
+
+ if (buf[0] != 'x') {
+ fprintf(stderr, "Pulled memory lacks the child's mark\n");
+ goto out;
+ }
+
+ fprintf(stderr, "Skeleton of %zd bytes, pulled %zu bytes of memory\n",
+ received, sizeof(buf));
+ }
+
+ exit_code = EXIT_SUCCESS;
+out:
+ close(addr_pipe[0]);
+ if (fd_file >= 0)
+ close(fd_file);
+ if (fd_peer_pidfd >= 0)
+ close(fd_peer_pidfd);
+ if (fd_coredump >= 0)
+ close(fd_coredump);
+ if (fd_server >= 0)
+ close(fd_server);
+ _exit(exit_code);
+ }
+ self->pid_coredump_server = pid_coredump_server;
+
+ EXPECT_EQ(close(ipc_sockets[1]), 0);
+ EXPECT_EQ(close(addr_pipe[0]), 0);
+ ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
+ EXPECT_EQ(close(ipc_sockets[0]), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+ if (pid == 0)
+ crashing_child_memory(choice->task_filter, addr_pipe[1]);
+ EXPECT_EQ(close(addr_pipe[1]), 0);
+
+ pidfd = sys_pidfd_open(pid, 0);
+ ASSERT_GE(pidfd, 0);
+
+ waitpid(pid, &status, 0);
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_TRUE(WCOREDUMP(status));
+
+ ASSERT_TRUE(get_pidfd_info(pidfd, &info));
+ ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
+ ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
+
+ wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
+}
+
+/* Without COREDUMP_MEMORY_TYPES the task's own selection decides. */
+TEST_F(coredump, socket_request_memory_types_task_includes)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .mask = COREDUMP_KERNEL,
+ .shared_dumped = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+TEST_F(coredump, socket_request_memory_types_task_excludes)
+{
+ struct memory_choice choice = {
+ .task_filter = 0,
+ .mask = COREDUMP_KERNEL,
+ .shared_dumped = false,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* The server drops a memory type the task would have dumped. */
+TEST_F(coredump, socket_request_memory_types_restricts)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE,
+ .shared_dumped = false,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* The server adds a memory type the task had excluded. */
+TEST_F(coredump, socket_request_memory_types_widens)
+{
+ struct memory_choice choice = {
+ .task_filter = 0,
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .shared_dumped = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* The memory types decide what goes into a record stream just the same. */
+TEST_F(coredump, socket_request_memory_types_records)
+{
+ struct memory_choice choice = {
+ .task_filter = FILTER_TASK_INHERIT,
+ .mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE,
+ .shared_dumped = false,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/*
+ * An empty selection leaves a skeleton: every program header and every note
+ * but no memory. A server that wants to pick the memory itself reads it
+ * from /proc/<pid>/mem while the task waits for it to finish.
+ */
+TEST_F(coredump, socket_request_memory_types_skeleton)
+{
+ struct memory_choice choice = {
+ .task_filter = FILTER_TASK_INHERIT,
+ .mask = COREDUMP_KERNEL | COREDUMP_WAIT | COREDUMP_MEMORY_TYPES,
+ .memory_types = 0,
+ .shared_dumped = false,
+ .skeleton = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* A memory type the kernel didn't advertise in memory_types_mask. */
+TEST_F(coredump, socket_request_memory_types_unknown_bit)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ .memory_types = 1ULL << 63,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_UNSUPPORTED,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* The memory types must be zero unless COREDUMP_MEMORY_TYPES is raised. */
+TEST_F(coredump, socket_request_memory_types_stale_field)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = COREDUMP_KERNEL,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_UNSUPPORTED,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* COREDUMP_MEMORY_TYPES needs an ack that has the memory types. */
+TEST_F(coredump, socket_request_memory_types_short_ack)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = COREDUMP_ACK_SIZE_VER0,
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ },
+ .bytes = COREDUMP_ACK_SIZE_VER0,
+ .mark = COREDUMP_MARK_MINSIZE,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* The memory types select what the kernel writes, nothing else. */
+TEST_F(coredump, socket_request_memory_types_without_kernel)
+{
+ check_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_MEMORY_TYPES);
+}
+
+/*
+ * A server built with the first structs reads the request it knows,
+ * discards the rest and acks with the ack it knows. It raises nothing
+ * it wasn't built for and the kernel dumps what the task selected.
+ */
+TEST_F(coredump, socket_request_negotiate_ver0)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .build = &server_build_ver0,
+ .mask = COREDUMP_KERNEL,
+ .memory_types = 0,
+ .size_ack = COREDUMP_ACK_SIZE_VER0,
+ .shared_dumped = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/*
+ * A server built against this header takes every feature the kernel
+ * offers, drops shared memory from what the task selected and adds the
+ * ELF headers.
+ */
+TEST_F(coredump, socket_request_negotiate_ver1)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .build = &server_build_ver1,
+ .mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ELF_HEADERS,
+ .size_ack = COREDUMP_ACK_SIZE_VER1,
+ .shared_dumped = false,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* An ack that picks none of KERNEL, USERSPACE and REJECT. */
+TEST_F(coredump, socket_request_no_mode)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .mask = COREDUMP_WAIT,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_CONFLICTING,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* @spare must be zero, like every field that isn't in use. */
+TEST_F(coredump, socket_request_spare)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = sizeof(struct coredump_ack),
+ .spare = 1,
+ .mask = COREDUMP_KERNEL,
+ },
+ .bytes = sizeof(struct coredump_ack),
+ .mark = COREDUMP_MARK_UNSUPPORTED,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* An ack size is a byte count. One that ends inside a field is valid. */
+#define ACK_SIZE_BETWEEN (COREDUMP_ACK_SIZE_VER0 + sizeof(__u32))
+
+/* Any size from VER0 up to what the kernel accepts works without memory types. */
+TEST_F(coredump, socket_request_ack_size_between)
+{
+ struct memory_choice choice = {
+ .task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .mask = COREDUMP_KERNEL,
+ .size_ack = ACK_SIZE_BETWEEN,
+ .shared_dumped = true,
+ };
+
+ check_memory_dump(_metadata, self, &choice);
+}
+
+/* The memory types need the whole field, not the part that happens to fit. */
+TEST_F(coredump, socket_request_memory_types_ack_size_between)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = ACK_SIZE_BETWEEN,
+ .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+ },
+ .bytes = ACK_SIZE_BETWEEN,
+ .mark = COREDUMP_MARK_MINSIZE,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* A server that hangs up without acking gets no marker and no coredump. */
+TEST_F(coredump, socket_request_server_hangs_up)
+{
+ struct refused_ack refused = {
+ .bytes = 0,
+ .no_marker = true,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/* A server that hangs up in the middle of its ack looks the same. */
+TEST_F(coredump, socket_request_ack_truncated)
+{
+ struct refused_ack refused = {
+ .ack = {
+ .size = COREDUMP_ACK_SIZE_VER0,
+ .mask = COREDUMP_KERNEL,
+ },
+ .bytes = COREDUMP_ACK_SIZE_VER0 / 2,
+ .no_marker = true,
+ };
+
+ check_refused_ack(_metadata, self, &refused);
+}
+
+/*
+ * The kernels a server built against this header can't meet here:
+ * negotiate() against their requests, no coredump involved.
+ */
+
+/* The request of a kernel with the first structs and features. */
+static const struct coredump_req req_ver0 = {
+ .size = COREDUMP_REQ_SIZE_VER0,
+ .size_ack = COREDUMP_ACK_SIZE_VER0,
+ .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
+ COREDUMP_REJECT | COREDUMP_WAIT,
+};
+
+/* The request of this kernel. */
+static const struct coredump_req req_ver1 = {
+ .size = COREDUMP_REQ_SIZE_VER1,
+ .size_ack = COREDUMP_ACK_SIZE_VER1,
+ .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
+ COREDUMP_REJECT | COREDUMP_WAIT |
+ COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES,
+ .memory_types = COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ANON_SHARED,
+ .memory_types_mask = TEST_MEMORY_ALL,
+};
+
+/* A kernel with the first structs gets the first ack and nothing newer. */
+TEST(negotiate_ver0_kernel)
+{
+ struct coredump_ack ack;
+
+ negotiate(&req_ver0, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL);
+ ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* A kernel with records and sparse but the first structs: both, no types. */
+TEST(negotiate_sparse_kernel)
+{
+ struct coredump_req req = req_ver0;
+ struct coredump_ack ack;
+
+ req.mask |= COREDUMP_RECORDS | COREDUMP_SPARSE;
+ negotiate(&req, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE);
+ ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* Records without sparse: sparse isn't raised on its own. */
+TEST(negotiate_records_without_sparse)
+{
+ struct coredump_req req = req_ver0;
+ struct coredump_ack ack;
+
+ req.mask |= COREDUMP_RECORDS;
+ negotiate(&req, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS);
+}
+
+/*
+ * A feature whose ack field lies past what the kernel accepts can't be
+ * raised. No kernel offers the memory types without the room for them, so a
+ * request that does stands in for a feature newer than this header.
+ */
+TEST(negotiate_types_need_room)
+{
+ struct coredump_req req = req_ver0;
+ struct coredump_ack ack;
+
+ req.mask |= COREDUMP_MEMORY_TYPES;
+ negotiate(&req, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL);
+ ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* This kernel: the policy applied to the task's selection. */
+TEST(negotiate_ver1_kernel)
+{
+ struct coredump_ack ack;
+
+ negotiate(&req_ver1, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER1);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES);
+ ASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE |
+ COREDUMP_MEMORY_ELF_HEADERS);
+}
+
+/* A kernel that doesn't know a type the policy adds isn't asked for it. */
+TEST(negotiate_unknown_type)
+{
+ struct coredump_req req = req_ver1;
+ struct coredump_ack ack;
+
+ req.memory_types_mask &= ~(__u64)COREDUMP_MEMORY_ELF_HEADERS;
+ negotiate(&req, &server_build_ver1, &ack);
+ ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+ COREDUMP_MEMORY_TYPES);
+ ASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE);
+}
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c
index d7cc448eeaf44..4e36e3e4fb78f 100644
--- a/tools/testing/selftests/coredump/coredump_test_helpers.c
+++ b/tools/testing/selftests/coredump/coredump_test_helpers.c
@@ -17,6 +17,7 @@
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
@@ -73,6 +74,48 @@ void crashing_child_sparse(size_t size)
*(volatile int *)NULL = 0;
}
+/* Select @types through the caller's own /proc/self/coredump_filter. */
+static bool set_coredump_filter(__u64 types)
+{
+ char buf[32];
+ int fd, len;
+ bool ok;
+
+ fd = open("/proc/self/coredump_filter", O_WRONLY | O_CLOEXEC);
+ if (fd < 0)
+ return false;
+
+ len = snprintf(buf, sizeof(buf), "0x%llx", (unsigned long long)types);
+ ok = write_nointr(fd, buf, len) == len;
+ close(fd);
+ return ok;
+}
+
+/*
+ * Map shared anonymous memory, touch it, tell the server where it is and
+ * crash. A @task_filter other than FILTER_TASK_INHERIT is selected first.
+ */
+void crashing_child_memory(__u64 task_filter, int fd_addr)
+{
+ char *p;
+
+ if (task_filter != FILTER_TASK_INHERIT && !set_coredump_filter(task_filter))
+ _exit(EXIT_FAILURE);
+
+ p = mmap(NULL, MEMORY_MAPPING_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ if (p == MAP_FAILED)
+ _exit(EXIT_FAILURE);
+ p[0] = 'x';
+
+ if (write_nointr(fd_addr, &p, sizeof(p)) != sizeof(p))
+ _exit(EXIT_FAILURE);
+ close(fd_addr);
+
+ /* crash on purpose */
+ *(volatile int *)NULL = 0;
+}
+
/* Sink a reassembled record stream is handed to, record by record. */
struct coredump_record_sink {
/* @len bytes of coredump data that belong at @offset. */
@@ -916,6 +959,82 @@ static const ElfW(Phdr) *find_segment(const ElfW(Phdr) *phdr, size_t nr,
return NULL;
}
+/* The PT_LOAD segment @vaddr falls into. */
+bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment)
+{
+ const ElfW(Phdr) *found;
+ ElfW(Phdr) *phdr;
+ size_t nr;
+
+ phdr = read_phdrs(fd, &nr);
+ if (!phdr)
+ return false;
+
+ found = find_segment(phdr, nr, vaddr);
+ if (found)
+ *segment = *found;
+ else
+ fprintf(stderr, "%s: no segment for 0x%llx\n", __func__,
+ (unsigned long long)vaddr);
+
+ free(phdr);
+ return found;
+}
+
+/* How many bytes the PT_LOAD and the PT_NOTE segments of @fd carry. */
+bool sum_coredump_segments(int fd, __u64 *data, __u64 *notes)
+{
+ ElfW(Phdr) *phdr;
+ size_t nr, i;
+
+ phdr = read_phdrs(fd, &nr);
+ if (!phdr)
+ return false;
+
+ *data = 0;
+ *notes = 0;
+ for (i = 0; i < nr; i++) {
+ if (phdr[i].p_type == PT_LOAD)
+ *data += phdr[i].p_filesz;
+ else if (phdr[i].p_type == PT_NOTE)
+ *notes += phdr[i].p_filesz;
+ }
+
+ free(phdr);
+ return true;
+}
+
+/* The coredump in @fd is at least as long as every segment it declares. */
+bool check_coredump_extent(int fd)
+{
+ ElfW(Phdr) *phdr;
+ struct stat st;
+ size_t nr, i;
+ bool ok = true;
+
+ if (fstat(fd, &st)) {
+ fprintf(stderr, "%s: fstat: %m\n", __func__);
+ return false;
+ }
+
+ phdr = read_phdrs(fd, &nr);
+ if (!phdr)
+ return false;
+
+ for (i = 0; i < nr; i++) {
+ if (phdr[i].p_offset + phdr[i].p_filesz <= (__u64)st.st_size)
+ continue;
+ fprintf(stderr, "%s: segment %zu ends at %llu, the coredump at %llu\n",
+ __func__, i,
+ (unsigned long long)(phdr[i].p_offset + phdr[i].p_filesz),
+ (unsigned long long)st.st_size);
+ ok = false;
+ }
+
+ free(phdr);
+ return ok;
+}
+
/* The next stretch of memory the segments cover, split ones merged back. */
static bool next_range(const ElfW(Phdr) *phdr, size_t nr, size_t *i,
__u64 *start, __u64 *end)
@@ -1250,6 +1369,62 @@ ssize_t peer_vm_size(int fd_peer_pidfd)
/* Protocol helper functions */
+/* The peer's /proc/<pid>/coredump_filter, which is in memory types. */
+bool peer_coredump_filter(int fd_peer_pidfd, __u64 *memory_types)
+{
+ struct pidfd_info info = {};
+ unsigned long value;
+ char path[64];
+ FILE *f;
+ int ret;
+
+ if (!get_pidfd_info(fd_peer_pidfd, &info))
+ return false;
+
+ snprintf(path, sizeof(path), "/proc/%d/coredump_filter", info.pid);
+ f = fopen(path, "r");
+ if (!f) {
+ fprintf(stderr, "%s: %s: %m\n", __func__, path);
+ return false;
+ }
+
+ ret = fscanf(f, "%lx", &value);
+ fclose(f);
+ if (ret != 1) {
+ fprintf(stderr, "%s: %s: no value\n", __func__, path);
+ return false;
+ }
+
+ *memory_types = value;
+ return true;
+}
+
+/* Read @len bytes at @addr from the peer's /proc/<pid>/mem. */
+ssize_t peer_read_mem(int fd_peer_pidfd, __u64 addr, void *buf, size_t len)
+{
+ struct pidfd_info info = {};
+ char path[64];
+ ssize_t ret;
+ int fd;
+
+ if (!get_pidfd_info(fd_peer_pidfd, &info))
+ return -1;
+
+ snprintf(path, sizeof(path), "/proc/%d/mem", info.pid);
+ fd = open(path, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ fprintf(stderr, "%s: %s: %m\n", __func__, path);
+ return -1;
+ }
+
+ ret = pread(fd, buf, len, addr);
+ if (ret < 0)
+ fprintf(stderr, "%s: %s at 0x%llx: %m\n", __func__, path,
+ (unsigned long long)addr);
+ close(fd);
+ return ret;
+}
+
ssize_t recv_marker(int fd)
{
enum coredump_mark mark = COREDUMP_MARK_REQACK;
@@ -1292,10 +1467,34 @@ bool read_marker(int fd, enum coredump_mark mark)
return ret == mark;
}
-bool read_coredump_req(int fd, struct coredump_req *req)
+/*
+ * The kernel hung up without sending anything more: end of stream, or a
+ * reset if it refused the ack on its peeked size and never read it.
+ */
+bool read_hangup(int fd)
{
ssize_t ret;
- size_t field_size, user_size, known_size, kernel_size, remaining_size;
+ char c;
+
+ ret = recv(fd, &c, sizeof(c), MSG_WAITALL);
+ if (ret == 0) {
+ fprintf(stderr, "Kernel closed the connection\n");
+ return true;
+ }
+ if (ret < 0 && errno == ECONNRESET) {
+ fprintf(stderr, "Kernel closed the connection with the ack unread\n");
+ return true;
+ }
+
+ fprintf(stderr, "%s: expected a hangup, got %zd: %m\n", __func__, ret);
+ return false;
+}
+
+/* Read the request as a server built with a @user_size byte struct does. */
+bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size)
+{
+ ssize_t ret;
+ size_t field_size, known_size, kernel_size, remaining_size;
memset(req, 0, sizeof(*req));
field_size = sizeof(req->size);
@@ -1303,25 +1502,24 @@ bool read_coredump_req(int fd, struct coredump_req *req)
/* Peek the size of the coredump request. */
ret = recv(fd, req, field_size, MSG_PEEK | MSG_WAITALL);
if (ret != field_size) {
- fprintf(stderr, "read_coredump_req: peek failed (got %zd, expected %zu): %m\n",
+ fprintf(stderr, "%s: peek failed (got %zd, expected %zu): %m\n", __func__,
ret, field_size);
return false;
}
kernel_size = req->size;
if (kernel_size < COREDUMP_REQ_SIZE_VER0) {
- fprintf(stderr, "read_coredump_req: kernel_size %zu < min %d\n",
+ fprintf(stderr, "%s: kernel_size %zu < min %d\n", __func__,
kernel_size, COREDUMP_REQ_SIZE_VER0);
return false;
}
if (kernel_size >= PAGE_SIZE) {
- fprintf(stderr, "read_coredump_req: kernel_size %zu >= PAGE_SIZE %d\n",
+ fprintf(stderr, "%s: kernel_size %zu >= PAGE_SIZE %d\n", __func__,
kernel_size, PAGE_SIZE);
return false;
}
/* Consume as much of the request as we know about. */
- user_size = sizeof(struct coredump_req);
known_size = user_size < kernel_size ? user_size : kernel_size;
ret = recv(fd, req, known_size, MSG_WAITALL);
if (ret != known_size)
@@ -1354,8 +1552,13 @@ bool read_coredump_req(int fd, struct coredump_req *req)
return true;
}
-bool send_coredump_ack(int fd, const struct coredump_req *req,
- __u64 mask, size_t size_ack)
+bool read_coredump_req(int fd, struct coredump_req *req)
+{
+ return read_coredump_req_sized(fd, req, sizeof(*req));
+}
+
+/* Send @len bytes of @ack as they are, more than the struct if asked to. */
+bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len)
{
ssize_t ret;
/*
@@ -1367,34 +1570,60 @@ bool send_coredump_ack(int fd, const struct coredump_req *req,
char buffer[PAGE_SIZE];
} large_ack = {};
- if (!size_ack)
- size_ack = sizeof(struct coredump_ack) < req->size_ack ?
- sizeof(struct coredump_ack) :
- req->size_ack;
- large_ack.ack.mask = mask;
- large_ack.ack.size = size_ack;
- ret = send(fd, &large_ack, size_ack, MSG_NOSIGNAL);
- if (ret != size_ack) {
+ if (len > sizeof(large_ack))
+ return false;
+
+ large_ack.ack = *ack;
+ ret = send(fd, &large_ack, len, MSG_NOSIGNAL);
+ if (ret != len) {
fprintf(stderr, "%s: short send %zd: %m\n", __func__, ret);
return false;
}
- fprintf(stderr, "Sent coredump ack with size %zu and mask 0x%llx\n",
- size_ack, (unsigned long long)mask);
+ fprintf(stderr, "Sent %zu bytes of coredump ack: size %u, mask 0x%llx, types 0x%llx\n",
+ len, ack->size, (unsigned long long)ack->mask,
+ (unsigned long long)ack->memory_types);
return true;
}
+bool send_coredump_ack_types(int fd, const struct coredump_req *req,
+ __u64 mask, __u64 memory_types, size_t size_ack)
+{
+ struct coredump_ack ack = {
+ .mask = mask,
+ .memory_types = memory_types,
+ };
+
+ if (!size_ack)
+ size_ack = sizeof(struct coredump_ack) < req->size_ack ?
+ sizeof(struct coredump_ack) :
+ req->size_ack;
+ ack.size = size_ack;
+ return send_coredump_ack_bytes(fd, &ack, size_ack);
+}
+
+bool send_coredump_ack(int fd, const struct coredump_req *req,
+ __u64 mask, size_t size_ack)
+{
+ return send_coredump_ack_types(fd, req, mask, 0, size_ack);
+}
+
/* Every option the kernel is expected to advertise in coredump_req->mask. */
#define TEST_REQ_MASK_ALL \
(COREDUMP_KERNEL | COREDUMP_USERSPACE | \
COREDUMP_REJECT | COREDUMP_WAIT | \
- COREDUMP_RECORDS | COREDUMP_SPARSE)
+ COREDUMP_RECORDS | COREDUMP_SPARSE | COREDUMP_MEMORY_TYPES)
bool check_coredump_req(const struct coredump_req *req)
{
- if (req->size < COREDUMP_REQ_SIZE_VER0) {
- fprintf(stderr, "%s: size %u below minimum %d\n",
- __func__, req->size, COREDUMP_REQ_SIZE_VER0);
+ if (req->size != COREDUMP_REQ_SIZE_VER1) {
+ fprintf(stderr, "%s: size %u, expected %d\n",
+ __func__, req->size, COREDUMP_REQ_SIZE_VER1);
+ return false;
+ }
+ if (req->size_ack != COREDUMP_ACK_SIZE_VER1) {
+ fprintf(stderr, "%s: size_ack %u, expected %d\n",
+ __func__, req->size_ack, COREDUMP_ACK_SIZE_VER1);
return false;
}
if (req->mask != TEST_REQ_MASK_ALL) {
@@ -1403,6 +1632,12 @@ bool check_coredump_req(const struct coredump_req *req)
(unsigned long long)TEST_REQ_MASK_ALL);
return false;
}
+ if (req->memory_types_mask != TEST_MEMORY_ALL) {
+ fprintf(stderr, "%s: memory_types_mask 0x%llx, expected 0x%llx\n",
+ __func__, (unsigned long long)req->memory_types_mask,
+ (unsigned long long)TEST_MEMORY_ALL);
+ return false;
+ }
return true;
}
diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h
index 97ad5cfeae928..3f2f87837558e 100644
--- a/tools/testing/selftests/coredump/coredump_test_helpers.h
+++ b/tools/testing/selftests/coredump/coredump_test_helpers.h
@@ -3,6 +3,7 @@
#ifndef __COREDUMP_TEST_HELPERS_H
#define __COREDUMP_TEST_HELPERS_H
+#include <link.h>
#include <stdbool.h>
#include <sys/types.h>
#include <linux/coredump.h>
@@ -21,10 +22,30 @@
/* A task mapping at least this much is worth a record stream. */
#define SPARSE_STREAM_THRESHOLD (SPARSE_MAPPING_SIZE / 2)
+/* Size of the shared anonymous mapping the memory types tests map. */
+#define MEMORY_MAPPING_SIZE (4 * 1024 * 1024)
+
+/* Leave the coredump_filter the crashing child inherited alone. */
+#define FILTER_TASK_INHERIT ((__u64)-1)
+
+/* Every memory type the kernel is expected to advertise. */
+#define TEST_MEMORY_ALL \
+ (COREDUMP_MEMORY_ANON_PRIVATE | COREDUMP_MEMORY_ANON_SHARED | \
+ COREDUMP_MEMORY_FILE_PRIVATE | COREDUMP_MEMORY_FILE_SHARED | \
+ COREDUMP_MEMORY_ELF_HEADERS | \
+ COREDUMP_MEMORY_HUGETLB_PRIVATE | COREDUMP_MEMORY_HUGETLB_SHARED | \
+ COREDUMP_MEMORY_DAX_PRIVATE | COREDUMP_MEMORY_DAX_SHARED)
+
/* Shared helper function declarations */
void *do_nothing(void *arg);
void crashing_child(void);
void crashing_child_sparse(size_t size);
+void crashing_child_memory(__u64 task_filter, int fd_addr);
+bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment);
+bool sum_coredump_segments(int fd, __u64 *data, __u64 *notes);
+bool check_coredump_extent(int fd);
+bool peer_coredump_filter(int fd_peer_pidfd, __u64 *memory_types);
+ssize_t peer_read_mem(int fd_peer_pidfd, __u64 addr, void *buf, size_t len);
ssize_t recv_coredump_records(int fd_coredump, int fd_core_file,
off_t *coredump_size, bool *truncated,
int fd_peer_pidfd);
@@ -43,9 +64,14 @@ bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);
/* Protocol helper function declarations */
ssize_t recv_marker(int fd);
bool read_marker(int fd, enum coredump_mark mark);
+bool read_hangup(int fd);
bool read_coredump_req(int fd, struct coredump_req *req);
+bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size);
bool send_coredump_ack(int fd, const struct coredump_req *req,
__u64 mask, size_t size_ack);
+bool send_coredump_ack_types(int fd, const struct coredump_req *req,
+ __u64 mask, __u64 memory_types, size_t size_ack);
+bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len);
bool check_coredump_req(const struct coredump_req *req);
int open_coredump_tmpfile(int fd_tmpfs_detached);
void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file);
|
| 53/3 | 2026/08/21 13:52 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 38778 output: 277 thoughts: 2634 Thoughts: **Analysis of Memory Safety in Coredump Socket Protocol Modification**
Alright, let's break this down. We're looking at a patch that introduces dynamic memory type selection for coredumps via the socket protocol. The core change revolves around adding the `COREDUMP_MEMORY_TYPES` flag and the `memory_types` and `memory_types_mask` fields to the `struct coredump_req` and `memory_types` field to the `struct coredump_ack`. My primary concern, as always, is memory safety – specifically, uninitialized memory usage, information leaks, and the like.
First, let's dissect the initialization of `struct coredump_req`. The original code used a designated initializer: `= { ... }`. This is excellent. It guarantees that *all* members of the structure are initialized, either explicitly or by zero-initialization if not explicitly set. Now, the critical question is: are there any padding bytes within this structure that could harbor garbage data? Let's check the structure definition: `__u32 size`, `__u32 size_ack`, `__u64 mask`, `__u64 memory_types`, `__u64 memory_types_mask`. Size calculation is 4 + 4 + 8 + 8 + 8 = 32 bytes. No padding. The socket send call transmits exactly `sizeof(*req)` bytes, so this is safe.
Next, we have `struct coredump_ack`. This structure is initialized with `= {}`, which means all members, *including any padding*, are explicitly zeroed out. The code then receives data into this zeroed structure via `coredump_sock_recv`. If less than `sizeof(ack)` bytes are received, the remaining fields, including padding, will remain zero. The code then checks various flags and compares received values. There's conditional logic, but it's carefully guarded. The `if (ack.mask & COREDUMP_MEMORY_TYPES)` block checks if `usize < COREDUMP_ACK_SIZE_VER1` if memory types are used. The subsequent checks ensure safe usage, and that the server will return an error code if there is conflicting values, or if the user doesn't support the values asked for.
I carefully examined the `vma_dump_size` and `do_coredump` functions, where memory types are used. In `vma_dump_size`, the `memory_types` variable is initialized from `COREDUMP_TASK_MEMORY_TYPES(mm)` or `ack.memory_types`. This is safe because `ack.memory_types` is zero-initialized and any modifications will be in a controlled environment. The `do_coredump` function uses `cprm`, which is also fully initialized.
Then, I double-checked `coredump_sock_send`. The function passes `req` by reference and sends `sizeof(*req)` bytes. The crucial point is the designated initializer `= { ... }` ensures all members, *including padding*, are zeroed out and valid before transmission. In C, `= { ... }` indeed zeroes any padding.
Therefore, after a thorough review, I can confidently state that this patch does *not* introduce any potential for uninitialized memory usage or information leaks. There are no new buffer allocations, and the existing code initialization and data reception are handled properly. KMSAN, with its focus on uninitialized memory, isn't necessary here. Standard KASAN should be sufficient to detect any out-of-bounds or use-after-free bugs that may still exist.
|
| 54/3 | 2026/08/21 13:53 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch modifies the coredump socket protocol to allow dynamic selection of memory types by introducing new fields to `struct coredump_req` and `struct coredump_ack`. \n\nThe `struct coredump_req` is sent to user space, but it is fully initialized on the stack using a designated initializer (`= { ... }`), which ensures all fields are properly initialized. Furthermore, the structure is perfectly aligned and contains no padding bytes (4+4+8+8+8 = 32 bytes), eliminating any risk of padding info-leaks.\n\nThe `struct coredump_ack` is received from user space, and it is explicitly zero-initialized (`= {}`) before the `recv` call, ensuring that any unreceived fields (e.g., when a shorter struct is sent by an older userspace) remain zeroed and do not contain uninitialized stack memory.\n\nThere are no new buffer allocations, no complex data structures with uninitialized fields, and no conditional logic depending on uninitialized variables. Any potential memory safety issues (like out-of-bounds accesses) would be caught by standard KASAN. Therefore, a dedicated KMSAN fuzzing session is not necessary.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch modifies the coredump socket protocol to allow dynamic selection of memory types by introducing new fields to `struct coredump_req` and `struct coredump_ack`. \n\nThe `struct coredump_req` is sent to user space, but it is fully initialized on the stack using a designated initializer (`= { ... }`), which ensures all fields are properly initialized. Furthermore, the structure is perfectly aligned and contains no padding bytes (4+4+8+8+8 = 32 bytes), eliminating any risk of padding info-leaks.\n\nThe `struct coredump_ack` is received from user space, and it is explicitly zero-initialized (`= {}`) before the `recv` call, ensuring that any unreceived fields (e.g., when a shorter struct is sent by an older userspace) remain zeroed and do not contain uninitialized stack memory.\n\nThere are no new buffer allocations, no complex data structures with uninitialized fields, and no conditional logic depending on uninitialized variables. Any potential memory safety issues (like out-of-bounds accesses) would be caught by standard KASAN. Therefore, a dedicated KMSAN fuzzing session is not necessary.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|