Merge patch series "coredump: select memory types per request"

Christian Brauner <brauner@kernel.org> says:

Currently /proc/<pid>/coredump_filter determines what types of memory
are included in a coredump produced by <pid>. This is fairly static. The
coredump server has no easy way to configure what memory to dump even
though it can figure out all the necessary details to make an informed
decision.

Add a new COREDUMP_MEMORY_TYPES feature bit. If the coredump server
raises it the kernel will dump memory types raised in the
coredump_ack->memory_types member. Zero is valid and causes the creation
of a coredump that just includes the program headers and notes but no
memory apart from the mappings that are always dumped.

struct coredump_req gains @memory_types which 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. It also gains
@memory_types_mask which 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_ack grows too. 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. 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.

@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.

* patches from https://patch.msgid.link/20260821-work-coredump-filter-v1-0-91f9a73ef03e@kernel.org:
  selftests/coredump: test failed handshakes
  selftests/coredump: improve coredump size negotiation tests
  selftests/coredump: test COREDUMP_MEMORY_TYPES
  selftests/coredump: simplify the refusal tests
  tools: sync coredump.h header
  coredump: select memory types to include

Link: https://patch.msgid.link/20260821-work-coredump-filter-v1-0-91f9a73ef03e@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-09-22 13:25:07 +02:00
commit 852d7eaa44
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
8 changed files with 1352 additions and 595 deletions

View File

@ -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
--------------------------------------------------------

View File

@ -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);
}
@ -1195,6 +1255,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;
@ -1206,8 +1270,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(),
@ -1741,15 +1805,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;
@ -1759,18 +1823,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;
}
@ -1782,25 +1850,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;
@ -1816,8 +1886,6 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
return DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER;
}
#undef FILTER
return 0;
whole:
@ -1902,7 +1970,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)

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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);