summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/filesystems/proc.rst4
-rw-r--r--fs/coredump.c114
-rw-r--r--include/linux/coredump.h4
-rw-r--r--include/uapi/linux/coredump.h70
-rw-r--r--tools/include/uapi/linux/coredump.h70
-rw-r--r--tools/testing/selftests/coredump/coredump_socket_protocol_test.c1232
-rw-r--r--tools/testing/selftests/coredump/coredump_test_helpers.c279
-rw-r--r--tools/testing/selftests/coredump/coredump_test_helpers.h26
8 files changed, 1278 insertions, 521 deletions
diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst
index c102b62023cd..fc59c98acca1 100644
--- a/Documentation/filesystems/proc.rst
+++ b/Documentation/filesystems/proc.rst
@@ -1963,6 +1963,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 dcaf998e54f6..33befe6b51c2 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);
}
@@ -1190,6 +1250,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;
@@ -1201,8 +1265,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(),
@@ -1736,15 +1800,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;
@@ -1754,18 +1818,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;
}
@@ -1777,25 +1845,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;
@@ -1811,8 +1881,6 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
return DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER;
}
-#undef FILTER
-
return 0;
whole:
@@ -1897,7 +1965,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 b252bb2843b3..74af57b9406b 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 f3771861ca48..6d0c53b534ea 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 f3771861ca48..6d0c53b534ea 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 daff908232a2..f5c9bad87546 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 @@ out:
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,362 +625,72 @@ out:
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-TEST_F(coredump, socket_request_unknown_flag)
+/* 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, 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_request_unknown_flag: 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");
- goto out;
- }
-
- close(ipc_sockets[1]);
-
- fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
- if (fd_coredump < 0) {
- fprintf(stderr, "socket_request_unknown_flag: 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");
- goto out;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_unknown_flag: 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");
- goto out;
- }
-
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_unknown_flag: 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");
- goto out;
- }
-
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_unknown_flag: check_coredump_req failed\n");
- goto out;
- }
-
- if (!send_coredump_ack(fd_coredump, &req, (1ULL << 63), 0)) {
- fprintf(stderr, "socket_request_unknown_flag: 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");
- goto out;
- }
-
- exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_unknown_flag: 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));
+ 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);
+}
- ASSERT_TRUE(get_pidfd_info(pidfd, &info));
- ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
- ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
+/* 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);
+}
- wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
+/* 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)
{
- 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_request_invalid_size_small: 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");
- 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_small: 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");
- goto out;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_invalid_size_small: 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");
- goto out;
- }
-
- if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_invalid_size_small: 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");
- goto out;
- }
-
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_invalid_size_small: check_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");
- goto out;
- }
-
- if (!read_marker(fd_coredump, COREDUMP_MARK_MINSIZE)) {
- fprintf(stderr, "socket_request_invalid_size_small: read_marker COREDUMP_MARK_MINSIZE failed\n");
- goto out;
- }
-
- exit_code = EXIT_SUCCESS;
- fprintf(stderr, "socket_request_invalid_size_small: 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);
+ 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)
{
- 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_request_invalid_size_large: 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_large: 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_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;
- }
-
- if (!get_pidfd_info(fd_peer_pidfd, &info)) {
- fprintf(stderr, "socket_request_invalid_size_large: get_pidfd_info failed\n");
- 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 (!(info.coredump_mask & PIDFD_COREDUMPED)) {
- fprintf(stderr, "socket_request_invalid_size_large: PIDFD_COREDUMPED not set in coredump_mask\n");
- goto out;
- }
-
- if (!read_coredump_req(fd_coredump, &req)) {
- fprintf(stderr, "socket_request_invalid_size_large: read_coredump_req failed\n");
- goto out;
- }
-
- if (!check_coredump_req(&req)) {
- fprintf(stderr, "socket_request_invalid_size_large: check_coredump_req failed\n");
- 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 (!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);
+ 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);
}
/*
@@ -2038,27 +1746,59 @@ out:
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)
+/* COREDUMP_RECORDS applies to a coredump the kernel writes, nothing else. */
+TEST_F(coredump, socket_request_records_without_kernel)
+{
+ check_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_RECORDS);
+}
+
+/* A zero record can't exist outside a record stream. */
+TEST_F(coredump, socket_request_sparse_without_records)
+{
+ check_conflicting_ack(_metadata, self, COREDUMP_KERNEL | COREDUMP_SPARSE);
+}
+
+/* What the server reports back about the coredump it decided to take. */
+struct stream_choice {
+ bool sparse;
+ ssize_t received;
+ off_t size;
+ ssize_t vm_size;
+};
+
+/*
+ * The kernel blocks in the coredump request until the ack arrives, so a
+ * coredump server gets to look at the task before it commits to a
+ * stream. Take the record stream only for a task whose mappings are
+ * worth it and the plain byte stream for everything else.
+ */
+static void check_stream_choice(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(coredump) *self, bool big,
+ struct stream_choice *choice)
{
int pidfd, status;
pid_t pid, pid_coredump_server;
struct pidfd_info info = {};
int ipc_sockets[2];
+ int pipefds[2];
char c;
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
+ ASSERT_EQ(pipe(pipefds), 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 stream_choice got = {};
+ __u64 mask;
close(ipc_sockets[0]);
+ close(pipefds[0]);
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
if (fd_server < 0)
@@ -2077,20 +1817,66 @@ static void check_conflicting_ack(struct __test_metadata *const _metadata,
if (fd_peer_pidfd < 0)
goto out;
+ /*
+ * The reassembled coredump is bigger than the mapping the
+ * child made, so keep it on the detached tmpfs and sparse.
+ */
+ fd_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
+ if (fd_file < 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))
+ /*
+ * Nothing is on the wire yet and the kernel is waiting for
+ * the ack, so there is all the time in the world to look at
+ * the task and decide what to ask it for.
+ */
+ got.vm_size = peer_vm_size(fd_peer_pidfd);
+ if (got.vm_size < 0)
+ goto out;
+ got.sparse = got.vm_size >= SPARSE_STREAM_THRESHOLD;
+
+ fprintf(stderr, "Peer maps %zd bytes, asking for %s\n",
+ got.vm_size,
+ got.sparse ? "a sparse record stream" : "a byte stream");
+
+ mask = COREDUMP_KERNEL | COREDUMP_WAIT;
+ if (got.sparse)
+ mask |= COREDUMP_RECORDS | COREDUMP_SPARSE;
+
+ if (!send_coredump_ack(fd_coredump, &req, mask, 0))
+ goto out;
+
+ if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK))
+ goto out;
+
+ if (got.sparse) {
+ got.received = recv_coredump_records(fd_coredump, fd_file,
+ &got.size, NULL, -1);
+ } else {
+ got.received = recv_coredump_bytes(fd_coredump, fd_file);
+ got.size = got.received;
+ }
+ if (got.received < 0)
goto out;
- if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING))
+ /* Either way a debugger has to see an ordinary core file. */
+ if (!is_elf_core(fd_file))
+ goto out;
+
+ if (write_nointr(pipefds[1], &got, sizeof(got)) != sizeof(got))
goto out;
exit_code = EXIT_SUCCESS;
out:
+ close(pipefds[1]);
+ if (fd_file >= 0)
+ close(fd_file);
if (fd_peer_pidfd >= 0)
close(fd_peer_pidfd);
if (fd_coredump >= 0)
@@ -2102,20 +1888,25 @@ out:
self->pid_coredump_server = pid_coredump_server;
EXPECT_EQ(close(ipc_sockets[1]), 0);
+ EXPECT_EQ(close(pipefds[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();
+ crashing_child_sparse(big ? SPARSE_MAPPING_SIZE : PAGE_SIZE);
pidfd = sys_pidfd_open(pid, 0);
ASSERT_GE(pidfd, 0);
waitpid(pid, &status, 0);
ASSERT_TRUE(WIFSIGNALED(status));
- ASSERT_FALSE(WCOREDUMP(status));
+ ASSERT_TRUE(WCOREDUMP(status));
+
+ ASSERT_EQ(read_nointr(pipefds[0], choice, sizeof(*choice)),
+ sizeof(*choice));
+ EXPECT_EQ(close(pipefds[0]), 0);
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
@@ -2124,45 +1915,131 @@ out:
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-/* COREDUMP_RECORDS applies to a coredump the kernel writes, nothing else. */
-TEST_F(coredump, socket_request_records_without_kernel)
+/* A task with little mapped isn't worth a record stream. */
+TEST_F(coredump, socket_request_stream_choice_small)
{
- check_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_RECORDS);
+ struct stream_choice choice = {};
+
+ check_stream_choice(_metadata, self, false, &choice);
+
+ ASSERT_LT(choice.vm_size, (ssize_t)SPARSE_STREAM_THRESHOLD);
+ ASSERT_FALSE(choice.sparse);
+ ASSERT_GT(choice.received, 0);
}
-/* A zero record can't exist outside a record stream. */
-TEST_F(coredump, socket_request_sparse_without_records)
+/* A task sitting on a big mapping is. */
+TEST_F(coredump, socket_request_stream_choice_large)
{
- check_conflicting_ack(_metadata, self, COREDUMP_KERNEL | COREDUMP_SPARSE);
+ struct stream_choice choice = {};
+
+ check_stream_choice(_metadata, self, true, &choice);
+
+ ASSERT_GE(choice.vm_size, (ssize_t)SPARSE_STREAM_THRESHOLD);
+ ASSERT_TRUE(choice.sparse);
+ ASSERT_GT(choice.size, (off_t)SPARSE_MAPPING_SIZE);
+
+ /* The holes didn't have to go over the socket. */
+ ASSERT_LT(choice.received, choice.size / 8);
}
-/* What the server reports back about the coredump it decided to take. */
-struct stream_choice {
- bool sparse;
- ssize_t received;
- off_t size;
- ssize_t vm_size;
+/* 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,
};
/*
- * The kernel blocks in the coredump request until the ack arrives, so a
- * coredump server gets to look at the task before it commits to a
- * stream. Take the record stream only for a task whose mappings are
- * worth it and the plain byte stream for everything else.
+ * 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 check_stream_choice(struct __test_metadata *const _metadata,
- FIXTURE_DATA(coredump) *self, bool big,
- struct stream_choice *choice)
+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 pipefds[2];
+ int addr_pipe[2];
char c;
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
- ASSERT_EQ(pipe(pipefds), 0);
+ ASSERT_EQ(pipe(addr_pipe), 0);
ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
pid_coredump_server = fork();
@@ -2172,11 +2049,21 @@ static void check_stream_choice(struct __test_metadata *const _metadata,
int fd_file = -1;
int exit_code = EXIT_FAILURE;
struct coredump_req req = {};
- struct stream_choice got = {};
- __u64 mask;
+ 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(pipefds[0]);
+ close(addr_pipe[1]);
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
if (fd_server < 0)
@@ -2195,64 +2082,127 @@ static void check_stream_choice(struct __test_metadata *const _metadata,
if (fd_peer_pidfd < 0)
goto out;
- /*
- * The reassembled coredump is bigger than the mapping the
- * child made, so keep it on the detached tmpfs and sparse.
- */
fd_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
if (fd_file < 0)
goto out;
- if (!read_coredump_req(fd_coredump, &req))
+ if (!read_coredump_req_sized(fd_coredump, &req, req_size))
goto out;
- if (!check_coredump_req(&req))
+ if (!peer_coredump_filter(fd_peer_pidfd, &task_filter))
goto out;
- /*
- * Nothing is on the wire yet and the kernel is waiting for
- * the ack, so there is all the time in the world to look at
- * the task and decide what to ask it for.
- */
- got.vm_size = peer_vm_size(fd_peer_pidfd);
- if (got.vm_size < 0)
+ /* 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;
- got.sparse = got.vm_size >= SPARSE_STREAM_THRESHOLD;
+ }
- fprintf(stderr, "Peer maps %zd bytes, asking for %s\n",
- got.vm_size,
- got.sparse ? "a sparse record stream" : "a byte stream");
+ /* The child sent the address of its mapping before it crashed. */
+ if (read_nointr(addr_pipe[0], &addr, sizeof(addr)) != sizeof(addr))
+ goto out;
- mask = COREDUMP_KERNEL | COREDUMP_WAIT;
- if (got.sparse)
- mask |= COREDUMP_RECORDS | COREDUMP_SPARSE;
+ /* A server build negotiates its ack and must arrive at the choice. */
+ if (choice->build) {
+ negotiate(&req, choice->build, &ack);
- if (!send_coredump_ack(fd_coredump, &req, mask, 0))
+ 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 (got.sparse) {
- got.received = recv_coredump_records(fd_coredump, fd_file,
- &got.size, NULL, -1);
- } else {
- got.received = recv_coredump_bytes(fd_coredump, fd_file);
- got.size = got.received;
- }
- if (got.received < 0)
+ 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;
- /* Either way a debugger has to see an ordinary core file. */
if (!is_elf_core(fd_file))
goto out;
- if (write_nointr(pipefds[1], &got, sizeof(got)) != sizeof(got))
+ /* 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, &notes))
+ 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(pipefds[1]);
+ close(addr_pipe[0]);
if (fd_file >= 0)
close(fd_file);
if (fd_peer_pidfd >= 0)
@@ -2266,14 +2216,15 @@ out:
self->pid_coredump_server = pid_coredump_server;
EXPECT_EQ(close(ipc_sockets[1]), 0);
- EXPECT_EQ(close(pipefds[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_sparse(big ? SPARSE_MAPPING_SIZE : PAGE_SIZE);
+ 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);
@@ -2282,10 +2233,6 @@ out:
ASSERT_TRUE(WIFSIGNALED(status));
ASSERT_TRUE(WCOREDUMP(status));
- ASSERT_EQ(read_nointr(pipefds[0], choice, sizeof(*choice)),
- sizeof(*choice));
- 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);
@@ -2293,31 +2240,376 @@ out:
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
-/* A task with little mapped isn't worth a record stream. */
-TEST_F(coredump, socket_request_stream_choice_small)
+/* Without COREDUMP_MEMORY_TYPES the task's own selection decides. */
+TEST_F(coredump, socket_request_memory_types_task_includes)
{
- struct stream_choice choice = {};
+ 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);
+}
- check_stream_choice(_metadata, self, false, &choice);
+TEST_F(coredump, socket_request_memory_types_task_excludes)
+{
+ struct memory_choice choice = {
+ .task_filter = 0,
+ .mask = COREDUMP_KERNEL,
+ .shared_dumped = false,
+ };
- ASSERT_LT(choice.vm_size, (ssize_t)SPARSE_STREAM_THRESHOLD);
- ASSERT_FALSE(choice.sparse);
- ASSERT_GT(choice.received, 0);
+ check_memory_dump(_metadata, self, &choice);
}
-/* A task sitting on a big mapping is. */
-TEST_F(coredump, socket_request_stream_choice_large)
+/* The server drops a memory type the task would have dumped. */
+TEST_F(coredump, socket_request_memory_types_restricts)
{
- struct stream_choice choice = {};
+ 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);
+}
- check_stream_choice(_metadata, self, true, &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);
+}
- ASSERT_GE(choice.vm_size, (ssize_t)SPARSE_STREAM_THRESHOLD);
- ASSERT_TRUE(choice.sparse);
- ASSERT_GT(choice.size, (off_t)SPARSE_MAPPING_SIZE);
+/* 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);
+}
- /* The holes didn't have to go over the socket. */
- ASSERT_LT(choice.received, choice.size / 8);
+/*
+ * 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 d7cc448eeaf4..4e36e3e4fb78 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 97ad5cfeae92..3f2f87837558 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);