mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge patch series "coredump: allow to create sparse coredumps on the coredump socket"
Christian Brauner <brauner@kernel.org> says: A coredump generated via the coredump socket ends up transferring zeroed data when a mapping contains holes. For a large process that maps a bunch of data that's wasting a ton of work. Jacob ran into this and Josef has bitched^wcomplained about this to me before. I dislike the coredump_filter bit solution in [1] which stops each PT_LOAD at the last populated page. The problem is real though. I don't think coredump_filter is where we need to solve this. That mask says which kinds of memory to include and it propagates across fork and exec, whereas what is being selected here is an encoding mechanism. I also think that the usermodehelper - may it swiftly die - isn't really salvagable for this and it's not the future anyway. The coredump socket already has a handshake for stuff like this. I always had an idea how this would look like but punted on it back then. So here it is. A server that raises COREDUMP_RECORDS in coredump_ack->mask doesn't get the coredump as a plain byte stream but as a sequence of records. Each one a struct coredump_record_header followed by what it describes. A data record carries its bytes. If a server also raises COREDUMP_SPARSE, zero records are sent for unpopulated mappings. They only indicate how many zero bytes need to be written and do not include data. Reassembling the records gives back the same coredump. A debugger and everything else still see an ordinary core file and nothing outside the coredump server has to learn anything. Numbers from the selftests, on a kernel built from this series: - a process with 128 threads: 1424153 bytes on the socket for a coredump of 1075150848 bytes - a 256MB mapping with the first and last page touched: 188793 bytes on the socket for a coredump of 268890112 bytes - the same 256MB mapping with COREDUMP_RECORDS alone: 271009312 bytes on the socket, so the record overhead itself is under one percent The first one is the interesting case. Almost all of it is thread stacks. All stacks are 8MB reservations that are nearly all holes. And they are holes in the middle of the dump rather than at the end. Link: https://lore.kernel.org/all/20260731171336.2255844-1-jalalonde@meta.com [1] * patches from https://patch.msgid.link/20260820-work-coredump-sparse-v2-0-ba32dd718c51@kernel.org: (21 commits) selftests/coredump: show how to inspect the task to decide how the coredump should be sent selftests/coredump: simulate a blob store selftests/coredump: put a hole in the middle of a sparse mapping selftests/coredump: hand the record stream to a sink selftests/coredump: test COREDUMP_RECORDS and COREDUMP_SPARSE coredump: describe the holes when COREDUMP_SPARSE is negotiated coredump: send the coredump in records if requested tools: sync coredump.h header coredump: add COREDUMP_SPARSE to the coredump socket protocol coredump: add COREDUMP_RECORDS to the coredump socket protocol coredump: clean up coredump state handling coredump: always chunk writes coredump: make the dump helper return bool coredump: deduplicate the to_skip flush coredump: move the negotiated mask into struct coredump_params coredump: pin the protocol struct sizes selftests/coredump: add a separate helper header selftests/coredump: collapse the expected request check into the helper selftests/coredump: discard the right amount after the coredump request coredump: set the minimum send buffer size ... Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-0-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
commit
9e46a55ed1
|
|
@ -74,7 +74,7 @@ static int load_elf_binary(struct linux_binprm *bprm);
|
|||
* don't even try.
|
||||
*/
|
||||
#ifdef CONFIG_ELF_CORE
|
||||
static int elf_core_dump(struct coredump_params *cprm);
|
||||
static bool elf_core_dump(struct coredump_params *cprm);
|
||||
#else
|
||||
#define elf_core_dump NULL
|
||||
#endif
|
||||
|
|
@ -1987,9 +1987,9 @@ static void fill_extnum_info(struct elfhdr *elf, struct elf_shdr *shdr4extnum,
|
|||
* and then they are actually written out. If we run out of core limit
|
||||
* we just truncate.
|
||||
*/
|
||||
static int elf_core_dump(struct coredump_params *cprm)
|
||||
static bool elf_core_dump(struct coredump_params *cprm)
|
||||
{
|
||||
int has_dumped = 0;
|
||||
bool ret = false;
|
||||
int segs, i;
|
||||
struct elfhdr elf;
|
||||
loff_t offset = 0, dataoff;
|
||||
|
|
@ -2020,7 +2020,7 @@ static int elf_core_dump(struct coredump_params *cprm)
|
|||
if (!fill_note_info(&elf, e_phnum, &info, cprm))
|
||||
goto end_coredump;
|
||||
|
||||
has_dumped = 1;
|
||||
cprm->state |= COREDUMP_STATE_STARTED;
|
||||
|
||||
offset += sizeof(elf); /* ELF header */
|
||||
offset += segs * sizeof(struct elf_phdr); /* Program headers */
|
||||
|
|
@ -2115,11 +2115,13 @@ static int elf_core_dump(struct coredump_params *cprm)
|
|||
goto end_coredump;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end_coredump:
|
||||
free_note_info(&info);
|
||||
kfree(shdr4extnum);
|
||||
kfree(phdr4note);
|
||||
return has_dumped;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_ELF_CORE */
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ static int elf_fdpic_map_file_by_direct_mmap(struct elf_fdpic_params *,
|
|||
struct file *, struct mm_struct *);
|
||||
|
||||
#ifdef CONFIG_ELF_CORE
|
||||
static int elf_fdpic_core_dump(struct coredump_params *cprm);
|
||||
static bool elf_fdpic_core_dump(struct coredump_params *cprm);
|
||||
#endif
|
||||
|
||||
static struct linux_binfmt elf_fdpic_format = {
|
||||
|
|
@ -1477,9 +1477,9 @@ static bool elf_fdpic_dump_segments(struct coredump_params *cprm,
|
|||
* and then they are actually written out. If we run out of core limit
|
||||
* we just truncate.
|
||||
*/
|
||||
static int elf_fdpic_core_dump(struct coredump_params *cprm)
|
||||
static bool elf_fdpic_core_dump(struct coredump_params *cprm)
|
||||
{
|
||||
int has_dumped = 0;
|
||||
bool ret = false;
|
||||
int segs;
|
||||
int i;
|
||||
struct elfhdr *elf = NULL;
|
||||
|
|
@ -1536,7 +1536,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
|
|||
/* Set up header */
|
||||
fill_elf_fdpic_header(elf, e_phnum);
|
||||
|
||||
has_dumped = 1;
|
||||
cprm->state |= COREDUMP_STATE_STARTED;
|
||||
/*
|
||||
* Set up the notes in similar form to SVR4 core dumps made
|
||||
* with info from their /proc.
|
||||
|
|
@ -1656,6 +1656,8 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
|
|||
cprm->file->f_pos, offset);
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end_coredump:
|
||||
while (thread_list) {
|
||||
tmp = thread_list;
|
||||
|
|
@ -1666,7 +1668,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
|
|||
kfree(elf);
|
||||
kfree(psinfo);
|
||||
kfree(shdr4extnum);
|
||||
return has_dumped;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_ELF_CORE */
|
||||
|
|
|
|||
325
fs/coredump.c
325
fs/coredump.c
|
|
@ -51,7 +51,6 @@
|
|||
#include <net/sock.h>
|
||||
#include <uapi/linux/pidfd.h>
|
||||
#include <uapi/linux/un.h>
|
||||
#include <uapi/linux/coredump.h>
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/mmu_context.h>
|
||||
|
|
@ -68,6 +67,8 @@
|
|||
|
||||
static bool dump_vma_snapshot(struct coredump_params *cprm);
|
||||
static void free_vma_snapshot(struct coredump_params *cprm);
|
||||
static void dump_end_record(struct coredump_params *cprm);
|
||||
static bool dump_flush_skip(struct coredump_params *cprm);
|
||||
|
||||
#define CORE_FILE_NOTE_SIZE_DEFAULT (4*1024*1024)
|
||||
/* Define a reasonable max cap */
|
||||
|
|
@ -98,9 +99,7 @@ struct core_name {
|
|||
char *corename __counted_by_ptr(size);
|
||||
int used, size;
|
||||
unsigned int core_pipe_limit;
|
||||
bool core_dumped;
|
||||
enum coredump_type_t core_type;
|
||||
u64 mask;
|
||||
};
|
||||
|
||||
static int expand_corename(struct core_name *cn, int size)
|
||||
|
|
@ -245,13 +244,12 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm,
|
|||
int pid_in_pattern = 0;
|
||||
int err = 0;
|
||||
|
||||
cn->mask = COREDUMP_KERNEL;
|
||||
cprm->mask = COREDUMP_KERNEL;
|
||||
if (core_pipe_limit)
|
||||
cn->mask |= COREDUMP_WAIT;
|
||||
cprm->mask |= COREDUMP_WAIT;
|
||||
cn->used = 0;
|
||||
cn->corename = NULL;
|
||||
cn->core_pipe_limit = 0;
|
||||
cn->core_dumped = false;
|
||||
if (*pat_ptr == '|')
|
||||
cn->core_type = COREDUMP_PIPE;
|
||||
else if (*pat_ptr == '@')
|
||||
|
|
@ -550,13 +548,13 @@ static int coredump_wait(int exit_code, struct core_state *core_state)
|
|||
return core_waiters;
|
||||
}
|
||||
|
||||
static void coredump_finish(bool core_dumped)
|
||||
static void coredump_finish(enum coredump_state state)
|
||||
{
|
||||
struct core_thread *curr, *next;
|
||||
struct task_struct *task;
|
||||
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
if (core_dumped && !__fatal_signal_pending(current))
|
||||
if ((state & COREDUMP_STATE_STARTED) && !__fatal_signal_pending(current))
|
||||
current->signal->group_exit_code |= 0x80;
|
||||
next = current->signal->core_state->dumper.next;
|
||||
current->signal->core_state = NULL;
|
||||
|
|
@ -664,7 +662,12 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static_assert(sizeof(struct coredump_record_header) == COREDUMP_RECORD_HEADER_SIZE_VER0);
|
||||
|
||||
#ifdef CONFIG_UNIX
|
||||
/* af_unix halves the send buffer to size a single skb. */
|
||||
#define COREDUMP_SOCK_SNDBUF_MIN (3 * PAGE_SIZE)
|
||||
|
||||
static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *cprm)
|
||||
{
|
||||
struct file *file __free(fput) = NULL;
|
||||
|
|
@ -690,6 +693,10 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
|
|||
if (retval < 0)
|
||||
return false;
|
||||
|
||||
/* Don't let a page-sized write split into several skbs. */
|
||||
socket->sk->sk_sndbuf = max_t(int, socket->sk->sk_sndbuf,
|
||||
COREDUMP_SOCK_SNDBUF_MIN);
|
||||
|
||||
file = sock_alloc_file(socket, 0, NULL);
|
||||
if (IS_ERR(file))
|
||||
return false;
|
||||
|
|
@ -752,6 +759,8 @@ 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(enum coredump_mark) == sizeof(__u32));
|
||||
|
||||
static inline bool coredump_sock_mark(struct file *file, enum coredump_mark mark)
|
||||
|
|
@ -797,7 +806,8 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
|
|||
struct coredump_req req = {
|
||||
.size = sizeof(struct coredump_req),
|
||||
.mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT,
|
||||
COREDUMP_REJECT | COREDUMP_WAIT |
|
||||
COREDUMP_RECORDS | COREDUMP_SPARSE,
|
||||
.size_ack = sizeof(struct coredump_ack),
|
||||
};
|
||||
struct coredump_ack ack = {};
|
||||
|
|
@ -851,7 +861,26 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
|
|||
return false;
|
||||
}
|
||||
|
||||
cn->mask = ack.mask;
|
||||
/* Records only describe a coredump the kernel writes. */
|
||||
if ((ack.mask & COREDUMP_RECORDS) && !(ack.mask & COREDUMP_KERNEL)) {
|
||||
coredump_sock_mark(cprm->file, COREDUMP_MARK_CONFLICTING);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Zero records only exist inside a record stream. */
|
||||
if ((ack.mask & COREDUMP_SPARSE) && !(ack.mask & COREDUMP_RECORDS)) {
|
||||
coredump_sock_mark(cprm->file, COREDUMP_MARK_CONFLICTING);
|
||||
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);
|
||||
if (!cprm->record_hdr)
|
||||
return false;
|
||||
}
|
||||
|
||||
cprm->mask = ack.mask;
|
||||
return coredump_sock_mark(cprm->file, COREDUMP_MARK_REQACK);
|
||||
}
|
||||
|
||||
|
|
@ -1032,29 +1061,41 @@ static bool coredump_pipe(struct core_name *cn, struct coredump_params *cprm,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool coredump_write(struct core_name *cn,
|
||||
struct coredump_params *cprm,
|
||||
const struct linux_binfmt *binfmt)
|
||||
static bool coredump_write(struct coredump_params *cprm,
|
||||
const struct linux_binfmt *binfmt)
|
||||
{
|
||||
|
||||
if (dump_interrupted())
|
||||
if (dump_interrupted()) {
|
||||
cprm->state |= COREDUMP_STATE_TRUNCATED;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!dump_vma_snapshot(cprm))
|
||||
if (!dump_vma_snapshot(cprm)) {
|
||||
cprm->state |= COREDUMP_STATE_TRUNCATED;
|
||||
return false;
|
||||
}
|
||||
|
||||
file_start_write(cprm->file);
|
||||
cn->core_dumped = binfmt->core_dump(cprm);
|
||||
if (!binfmt->core_dump(cprm))
|
||||
cprm->state |= COREDUMP_STATE_TRUNCATED;
|
||||
/*
|
||||
* Ensures that file size is big enough to contain the current
|
||||
* file postion. This prevents gdb from complaining about
|
||||
* a truncated file if the last "write" to the file was
|
||||
* dump_skip.
|
||||
* A trailing hole still has to land in the coredump. Seeking over
|
||||
* it doesn't grow the file, so the last byte of it is written
|
||||
* instead and gdb doesn't see a truncated file. Everything else
|
||||
* puts the hole on the wire as it flushes it.
|
||||
*/
|
||||
if (cprm->to_skip) {
|
||||
cprm->to_skip--;
|
||||
dump_emit(cprm, "", 1);
|
||||
bool flushed;
|
||||
|
||||
if (cprm->file->f_mode & FMODE_LSEEK) {
|
||||
cprm->to_skip--;
|
||||
flushed = dump_emit(cprm, "", 1);
|
||||
} else {
|
||||
flushed = dump_flush_skip(cprm);
|
||||
}
|
||||
if (!flushed)
|
||||
cprm->state |= COREDUMP_STATE_TRUNCATED;
|
||||
}
|
||||
dump_end_record(cprm);
|
||||
file_end_write(cprm->file);
|
||||
free_vma_snapshot(cprm);
|
||||
return true;
|
||||
|
|
@ -1069,7 +1110,8 @@ static void coredump_cleanup(struct core_name *cn, struct coredump_params *cprm)
|
|||
atomic_dec(&core_pipe_count);
|
||||
}
|
||||
kfree(cn->corename);
|
||||
coredump_finish(cn->core_dumped);
|
||||
kfree(cprm->record_hdr);
|
||||
coredump_finish(cprm->state);
|
||||
}
|
||||
|
||||
static inline bool coredump_skip(const struct coredump_params *cprm,
|
||||
|
|
@ -1115,7 +1157,7 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm,
|
|||
}
|
||||
|
||||
/* Don't even generate the coredump. */
|
||||
if (cn->mask & COREDUMP_REJECT)
|
||||
if (cprm->mask & COREDUMP_REJECT)
|
||||
return;
|
||||
|
||||
/* get us an unshared descriptor table; almost always a no-op */
|
||||
|
|
@ -1123,21 +1165,21 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm,
|
|||
if (unshare_files())
|
||||
return;
|
||||
|
||||
if ((cn->mask & COREDUMP_KERNEL) && !coredump_write(cn, cprm, binfmt))
|
||||
if ((cprm->mask & COREDUMP_KERNEL) && !coredump_write(cprm, binfmt))
|
||||
return;
|
||||
|
||||
coredump_sock_shutdown(cprm->file);
|
||||
|
||||
/* Let the parent know that a coredump was generated. */
|
||||
if (cn->mask & COREDUMP_USERSPACE)
|
||||
cn->core_dumped = true;
|
||||
if (cprm->mask & COREDUMP_USERSPACE)
|
||||
cprm->state |= COREDUMP_STATE_STARTED;
|
||||
|
||||
/*
|
||||
* When core_pipe_limit is set we wait for the coredump server
|
||||
* or usermodehelper to finish before exiting so it can e.g.,
|
||||
* inspect /proc/<pid>.
|
||||
*/
|
||||
if (cn->mask & COREDUMP_WAIT) {
|
||||
if (cprm->mask & COREDUMP_WAIT) {
|
||||
switch (cn->core_type) {
|
||||
case COREDUMP_PIPE:
|
||||
wait_for_dump_helpers(cprm->file);
|
||||
|
|
@ -1202,60 +1244,181 @@ void vfs_coredump(const kernel_siginfo_t *siginfo)
|
|||
* do on a core-file: use only these functions to write out all the
|
||||
* necessary info.
|
||||
*/
|
||||
static int __dump_emit(struct coredump_params *cprm, const void *addr, int nr)
|
||||
static bool dump_records(const struct coredump_params *cprm)
|
||||
{
|
||||
return cprm->mask & COREDUMP_RECORDS;
|
||||
}
|
||||
|
||||
static bool dump_sparse(const struct coredump_params *cprm)
|
||||
{
|
||||
return cprm->mask & COREDUMP_SPARSE;
|
||||
}
|
||||
|
||||
/* Describe the next @len bytes of the coredump. Returns the header size. */
|
||||
static size_t dump_record_init(struct coredump_params *cprm,
|
||||
enum coredump_record_type type, u64 flags,
|
||||
u64 len)
|
||||
{
|
||||
if (!dump_records(cprm))
|
||||
return 0;
|
||||
|
||||
*cprm->record_hdr = (struct coredump_record_header) {
|
||||
.size = sizeof(*cprm->record_hdr),
|
||||
.type = type,
|
||||
.flags = flags,
|
||||
.offset = cprm->pos,
|
||||
.len = len,
|
||||
};
|
||||
|
||||
return sizeof(*cprm->record_hdr);
|
||||
}
|
||||
|
||||
/* Write @iter whole or fail. @len is what it advances the coredump by. */
|
||||
static bool dump_write_iter(struct coredump_params *cprm, struct iov_iter *iter,
|
||||
size_t len)
|
||||
{
|
||||
struct file *file = cprm->file;
|
||||
size_t count = iov_iter_count(iter);
|
||||
loff_t pos = file->f_pos;
|
||||
ssize_t n;
|
||||
|
||||
if (cprm->written + nr > cprm->limit)
|
||||
return 0;
|
||||
if (dump_interrupted())
|
||||
return 0;
|
||||
n = __kernel_write(file, addr, nr, &pos);
|
||||
if (n != nr)
|
||||
return 0;
|
||||
n = __kernel_write_iter(file, iter, &pos);
|
||||
if (n != (ssize_t)count)
|
||||
return false;
|
||||
file->f_pos = pos;
|
||||
cprm->written += n;
|
||||
cprm->pos += n;
|
||||
cprm->written += count;
|
||||
cprm->pos += len;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int __dump_skip(struct coredump_params *cprm, size_t nr)
|
||||
/* One record, never more than a page. See __dump_emit(). */
|
||||
static bool dump_emit_chunk(struct coredump_params *cprm, const void *addr,
|
||||
int nr)
|
||||
{
|
||||
struct kvec kvec[2];
|
||||
struct iov_iter iter;
|
||||
unsigned int nseg = 0;
|
||||
size_t hdrlen;
|
||||
|
||||
if (dump_interrupted())
|
||||
return false;
|
||||
|
||||
hdrlen = dump_record_init(cprm, COREDUMP_RECORD_DATA, 0, nr);
|
||||
if (hdrlen) {
|
||||
kvec[nseg].iov_base = cprm->record_hdr;
|
||||
kvec[nseg].iov_len = hdrlen;
|
||||
nseg++;
|
||||
}
|
||||
kvec[nseg].iov_base = (void *)addr;
|
||||
kvec[nseg].iov_len = nr;
|
||||
nseg++;
|
||||
|
||||
iov_iter_kvec(&iter, ITER_SOURCE, kvec, nseg, hdrlen + nr);
|
||||
|
||||
return dump_write_iter(cprm, &iter, nr);
|
||||
}
|
||||
|
||||
static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr)
|
||||
{
|
||||
if (cprm->written + nr > cprm->limit)
|
||||
return false;
|
||||
|
||||
while (nr) {
|
||||
int chunk = min_t(int, nr, PAGE_SIZE);
|
||||
|
||||
if (!dump_emit_chunk(cprm, addr, chunk))
|
||||
return false;
|
||||
|
||||
addr += chunk;
|
||||
nr -= chunk;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Send a record that stands on its own: a header and nothing else. */
|
||||
static bool dump_emit_record(struct coredump_params *cprm,
|
||||
enum coredump_record_type type, u64 flags, u64 len)
|
||||
{
|
||||
struct kvec kvec;
|
||||
struct iov_iter iter;
|
||||
size_t hdrlen;
|
||||
|
||||
hdrlen = dump_record_init(cprm, type, flags, len);
|
||||
if (!hdrlen)
|
||||
return false;
|
||||
|
||||
kvec.iov_base = cprm->record_hdr;
|
||||
kvec.iov_len = hdrlen;
|
||||
iov_iter_kvec(&iter, ITER_SOURCE, &kvec, 1, hdrlen);
|
||||
|
||||
return dump_write_iter(cprm, &iter, len);
|
||||
}
|
||||
|
||||
/* Close the record stream. Only a whole coredump gets an end record. */
|
||||
static void dump_end_record(struct coredump_params *cprm)
|
||||
{
|
||||
if (cprm->state & COREDUMP_STATE_TRUNCATED)
|
||||
return;
|
||||
|
||||
dump_emit_record(cprm, COREDUMP_RECORD_END, 0, 0);
|
||||
}
|
||||
|
||||
static bool __dump_skip(struct coredump_params *cprm, size_t nr)
|
||||
{
|
||||
static char zeroes[PAGE_SIZE];
|
||||
struct file *file = cprm->file;
|
||||
|
||||
if (dump_sparse(cprm)) {
|
||||
/* Hand the server the length of the hole instead of the hole itself. */
|
||||
if (dump_interrupted())
|
||||
return false;
|
||||
return dump_emit_record(cprm, COREDUMP_RECORD_ZERO, 0, nr);
|
||||
}
|
||||
|
||||
if (file->f_mode & FMODE_LSEEK) {
|
||||
if (dump_interrupted() || vfs_llseek(file, nr, SEEK_CUR) < 0)
|
||||
return 0;
|
||||
return false;
|
||||
cprm->pos += nr;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
while (nr > PAGE_SIZE) {
|
||||
if (!__dump_emit(cprm, zeroes, PAGE_SIZE))
|
||||
return 0;
|
||||
nr -= PAGE_SIZE;
|
||||
while (nr) {
|
||||
size_t chunk = min_t(size_t, nr, PAGE_SIZE);
|
||||
|
||||
if (!__dump_emit(cprm, zeroes, chunk))
|
||||
return false;
|
||||
|
||||
nr -= chunk;
|
||||
}
|
||||
|
||||
return __dump_emit(cprm, zeroes, nr);
|
||||
return true;
|
||||
}
|
||||
|
||||
int dump_emit(struct coredump_params *cprm, const void *addr, int nr)
|
||||
/* Flush the accumulated hole before writing data. */
|
||||
static bool dump_flush_skip(struct coredump_params *cprm)
|
||||
{
|
||||
if (cprm->to_skip) {
|
||||
if (!__dump_skip(cprm, cprm->to_skip))
|
||||
return 0;
|
||||
return false;
|
||||
cprm->to_skip = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dump_emit(struct coredump_params *cprm, const void *addr, int nr)
|
||||
{
|
||||
if (!dump_flush_skip(cprm))
|
||||
return false;
|
||||
return __dump_emit(cprm, addr, nr);
|
||||
}
|
||||
EXPORT_SYMBOL(dump_emit);
|
||||
|
||||
void dump_skip_to(struct coredump_params *cprm, unsigned long pos)
|
||||
{
|
||||
if (WARN_ON_ONCE(pos < cprm->pos))
|
||||
return;
|
||||
cprm->to_skip = pos - cprm->pos;
|
||||
}
|
||||
EXPORT_SYMBOL(dump_skip_to);
|
||||
|
|
@ -1267,37 +1430,32 @@ void dump_skip(struct coredump_params *cprm, size_t nr)
|
|||
EXPORT_SYMBOL(dump_skip);
|
||||
|
||||
#ifdef CONFIG_ELF_CORE
|
||||
static int dump_emit_page(struct coredump_params *cprm, struct page *page)
|
||||
static bool dump_emit_page(struct coredump_params *cprm, struct page *page)
|
||||
{
|
||||
struct bio_vec bvec;
|
||||
struct bio_vec bvec[2];
|
||||
struct iov_iter iter;
|
||||
struct file *file = cprm->file;
|
||||
loff_t pos;
|
||||
ssize_t n;
|
||||
unsigned int nseg = 0;
|
||||
size_t hdrlen;
|
||||
|
||||
if (!page)
|
||||
return 0;
|
||||
return false;
|
||||
|
||||
if (cprm->to_skip) {
|
||||
if (!__dump_skip(cprm, cprm->to_skip))
|
||||
return 0;
|
||||
cprm->to_skip = 0;
|
||||
}
|
||||
if (!dump_flush_skip(cprm))
|
||||
return false;
|
||||
if (cprm->written + PAGE_SIZE > cprm->limit)
|
||||
return 0;
|
||||
return false;
|
||||
if (dump_interrupted())
|
||||
return 0;
|
||||
pos = file->f_pos;
|
||||
bvec_set_page(&bvec, page, PAGE_SIZE, 0);
|
||||
iov_iter_bvec(&iter, ITER_SOURCE, &bvec, 1, PAGE_SIZE);
|
||||
n = __kernel_write_iter(cprm->file, &iter, &pos);
|
||||
if (n != PAGE_SIZE)
|
||||
return 0;
|
||||
file->f_pos = pos;
|
||||
cprm->written += PAGE_SIZE;
|
||||
cprm->pos += PAGE_SIZE;
|
||||
return false;
|
||||
|
||||
return 1;
|
||||
/* Hand the record header to the same write as the page it describes. */
|
||||
hdrlen = dump_record_init(cprm, COREDUMP_RECORD_DATA, 0, PAGE_SIZE);
|
||||
if (hdrlen)
|
||||
bvec_set_virt(&bvec[nseg++], cprm->record_hdr, hdrlen);
|
||||
bvec_set_page(&bvec[nseg++], page, PAGE_SIZE, 0);
|
||||
|
||||
iov_iter_bvec(&iter, ITER_SOURCE, bvec, nseg, hdrlen + PAGE_SIZE);
|
||||
|
||||
return dump_write_iter(cprm, &iter, PAGE_SIZE);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1329,18 +1487,19 @@ static inline struct page *dump_page_copy(struct page *src, struct page *dst)
|
|||
}
|
||||
#endif
|
||||
|
||||
int dump_user_range(struct coredump_params *cprm, unsigned long start,
|
||||
unsigned long len)
|
||||
bool dump_user_range(struct coredump_params *cprm, unsigned long start,
|
||||
unsigned long len)
|
||||
{
|
||||
unsigned long addr;
|
||||
struct page *dump_page;
|
||||
int locked, ret;
|
||||
int locked;
|
||||
bool ret;
|
||||
|
||||
dump_page = dump_page_alloc();
|
||||
if (!dump_page)
|
||||
return 0;
|
||||
return false;
|
||||
|
||||
ret = 0;
|
||||
ret = false;
|
||||
locked = 0;
|
||||
for (addr = start; addr < start + len; addr += PAGE_SIZE) {
|
||||
struct page *page;
|
||||
|
|
@ -1364,7 +1523,7 @@ int dump_user_range(struct coredump_params *cprm, unsigned long start,
|
|||
mmap_read_unlock(current->mm);
|
||||
locked = 0;
|
||||
}
|
||||
int stop = !dump_emit_page(cprm, dump_page_copy(page, dump_page));
|
||||
bool stop = !dump_emit_page(cprm, dump_page_copy(page, dump_page));
|
||||
put_page(page);
|
||||
if (stop)
|
||||
goto out;
|
||||
|
|
@ -1383,7 +1542,7 @@ int dump_user_range(struct coredump_params *cprm, unsigned long start,
|
|||
}
|
||||
cond_resched();
|
||||
}
|
||||
ret = 1;
|
||||
ret = true;
|
||||
out:
|
||||
if (locked)
|
||||
mmap_read_unlock(current->mm);
|
||||
|
|
@ -1393,14 +1552,14 @@ int dump_user_range(struct coredump_params *cprm, unsigned long start,
|
|||
}
|
||||
#endif
|
||||
|
||||
int dump_align(struct coredump_params *cprm, int align)
|
||||
bool dump_align(struct coredump_params *cprm, int align)
|
||||
{
|
||||
unsigned mod = (cprm->pos + cprm->to_skip) & (align - 1);
|
||||
if (align & (align - 1))
|
||||
return 0;
|
||||
return false;
|
||||
if (mod)
|
||||
cprm->to_skip += align - mod;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL(dump_align);
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,8 @@ struct linux_binfmt {
|
|||
struct module *module;
|
||||
int (*load_binary)(struct linux_binprm *);
|
||||
#ifdef CONFIG_COREDUMP
|
||||
int (*core_dump)(struct coredump_params *cprm);
|
||||
/* Returns true if the whole coredump was written. */
|
||||
bool (*core_dump)(struct coredump_params *cprm);
|
||||
unsigned long min_coredump; /* minimal dump size */
|
||||
#endif
|
||||
} __randomize_layout;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,20 @@
|
|||
#include <linux/mm.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/sched/coredump.h>
|
||||
#include <uapi/linux/coredump.h>
|
||||
#include <asm/siginfo.h>
|
||||
|
||||
#ifdef CONFIG_COREDUMP
|
||||
/**
|
||||
* enum coredump_state - what happened while the coredump was written
|
||||
* @COREDUMP_STATE_STARTED: the dumper committed to writing a coredump
|
||||
* @COREDUMP_STATE_TRUNCATED: the dumper stopped before it had written all of it
|
||||
*/
|
||||
enum coredump_state {
|
||||
COREDUMP_STATE_STARTED = (1U << 0),
|
||||
COREDUMP_STATE_TRUNCATED = (1U << 1),
|
||||
};
|
||||
|
||||
struct core_vma_metadata {
|
||||
unsigned long start, end;
|
||||
vm_flags_t flags;
|
||||
|
|
@ -26,7 +37,15 @@ struct coredump_params {
|
|||
/* Snapshot of dumpable at dump start. */
|
||||
enum task_dumpable dumpable;
|
||||
int cpu;
|
||||
/* COREDUMP_* options negotiated with the coredump server. */
|
||||
u64 mask;
|
||||
/* COREDUMP_STATE_* raised while the coredump is written. */
|
||||
enum coredump_state state;
|
||||
/* Record header scratch, NULL unless the coredump is a record stream. */
|
||||
struct coredump_record_header *record_hdr;
|
||||
/* Bytes handed to the file, record headers included. */
|
||||
loff_t written;
|
||||
/* Offset in the coredump, record headers excluded. */
|
||||
loff_t pos;
|
||||
loff_t to_skip;
|
||||
int vma_count;
|
||||
|
|
@ -41,13 +60,13 @@ extern unsigned int core_file_note_size_limit;
|
|||
* These are the only things you should do on a core-file: use only these
|
||||
* functions to write out all the necessary info.
|
||||
*/
|
||||
extern void dump_skip_to(struct coredump_params *cprm, unsigned long to);
|
||||
extern void dump_skip(struct coredump_params *cprm, size_t nr);
|
||||
extern int dump_emit(struct coredump_params *cprm, const void *addr, int nr);
|
||||
extern int dump_align(struct coredump_params *cprm, int align);
|
||||
int dump_user_range(struct coredump_params *cprm, unsigned long start,
|
||||
unsigned long len);
|
||||
extern void vfs_coredump(const kernel_siginfo_t *siginfo);
|
||||
void dump_skip_to(struct coredump_params *cprm, unsigned long to);
|
||||
void dump_skip(struct coredump_params *cprm, size_t nr);
|
||||
bool dump_emit(struct coredump_params *cprm, const void *addr, int nr);
|
||||
bool dump_align(struct coredump_params *cprm, int align);
|
||||
bool dump_user_range(struct coredump_params *cprm, unsigned long start,
|
||||
unsigned long len);
|
||||
void vfs_coredump(const kernel_siginfo_t *siginfo);
|
||||
|
||||
/*
|
||||
* Logging for the coredump code, ratelimited.
|
||||
|
|
|
|||
|
|
@ -11,12 +11,19 @@
|
|||
* @COREDUMP_USERSPACE: userspace writes coredump
|
||||
* @COREDUMP_REJECT: don't generate coredump
|
||||
* @COREDUMP_WAIT: wait for coredump server
|
||||
* @COREDUMP_RECORDS: send the coredump as a sequence of records instead of
|
||||
* as a plain byte stream, see struct coredump_record_header;
|
||||
* requires COREDUMP_KERNEL
|
||||
* @COREDUMP_SPARSE: describe the holes in the coredump as zero records
|
||||
* instead of transferring them; requires COREDUMP_RECORDS
|
||||
*/
|
||||
enum {
|
||||
COREDUMP_KERNEL = (1ULL << 0),
|
||||
COREDUMP_USERSPACE = (1ULL << 1),
|
||||
COREDUMP_REJECT = (1ULL << 2),
|
||||
COREDUMP_WAIT = (1ULL << 3),
|
||||
COREDUMP_RECORDS = (1ULL << 4),
|
||||
COREDUMP_SPARSE = (1ULL << 5),
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -30,11 +37,11 @@ enum {
|
|||
* member is set to the size of struct coredump_req and provides a hint
|
||||
* to userspace how much data can be read. Userspace may use MSG_PEEK to
|
||||
* peek the size of struct coredump_req and then choose to consume it in
|
||||
* one go. Userspace may also simply read a COREDUMP_ACK_SIZE_VER0
|
||||
* one go. Userspace may also simply read a COREDUMP_REQ_SIZE_VER0
|
||||
* request. If the size the kernel sends is larger userspace simply
|
||||
* discards any remaining data.
|
||||
*
|
||||
* The coredump_req->mask member is set to the currently know features.
|
||||
* The coredump_req->mask member is set to the currently known features.
|
||||
* Userspace may only set coredump_ack->mask to the bits raised by the
|
||||
* kernel in coredump_req->mask.
|
||||
*
|
||||
|
|
@ -101,4 +108,72 @@ enum coredump_mark {
|
|||
__COREDUMP_MARK_MAX = (1U << 31),
|
||||
};
|
||||
|
||||
/**
|
||||
* enum coredump_record_type - Type of a coredump record
|
||||
*
|
||||
* @COREDUMP_RECORD_DATA: the header is followed by ->len bytes of data
|
||||
* @COREDUMP_RECORD_END: the coredump ends here, the header is not followed
|
||||
* by any data and no further record is sent
|
||||
* @COREDUMP_RECORD_ZERO: the header stands for ->len zero bytes and is not
|
||||
* followed by any data
|
||||
* @__COREDUMP_RECORD_TYPE_MAX: the maximum coredump record type value
|
||||
*/
|
||||
enum coredump_record_type {
|
||||
COREDUMP_RECORD_DATA = 0U,
|
||||
COREDUMP_RECORD_END = 1U,
|
||||
COREDUMP_RECORD_ZERO = 2U,
|
||||
__COREDUMP_RECORD_TYPE_MAX = (1U << 31),
|
||||
};
|
||||
|
||||
/**
|
||||
* struct coredump_record_header - header of a coredump record
|
||||
* @size: size of struct coredump_record_header
|
||||
* @type: one of enum coredump_record_type
|
||||
* @flags: modifiers for this record
|
||||
* @offset: offset in the coredump this record starts at
|
||||
* @len: number of coredump bytes this record accounts for
|
||||
*
|
||||
* If the coredump server raises COREDUMP_RECORDS in coredump_ack->mask
|
||||
* the kernel doesn't send the coredump as a plain byte stream. It sends
|
||||
* a sequence of records instead. A COREDUMP_RECORD_DATA record is
|
||||
* followed by @len bytes of actual coredump data. A
|
||||
* COREDUMP_RECORD_ZERO record is followed by nothing and stands for
|
||||
* @len zero bytes. A server that didn't raise COREDUMP_SPARSE never
|
||||
* sees a zero record. Records arrive in order and leave no gaps. So
|
||||
* @offset is the sum of the @len of all records before it.
|
||||
*
|
||||
* The last record is a COREDUMP_RECORD_END record. It is followed by
|
||||
* nothing. Its @len is zero. Its @offset is the size of the coredump.
|
||||
* The kernel only sends it once it has written the whole coredump. A
|
||||
* server that hits end-of-file without having seen an end record must
|
||||
* treat the coredump as incomplete.
|
||||
*
|
||||
* The @size member is set to the size of struct coredump_record_header
|
||||
* the kernel knows and lets the header grow later. It comes first so it
|
||||
* can be peeked. Userspace must consume @size bytes and discard
|
||||
* anything beyond what it knows. It must refuse a @size smaller than
|
||||
* COREDUMP_RECORD_HEADER_SIZE_VER0. @size covers the header alone.
|
||||
* @offset and @len count coredump bytes.
|
||||
*
|
||||
* The @flags member carries modifiers that change how the record is to
|
||||
* be interpreted. No flag is defined yet. Userspace must refuse a
|
||||
* record carrying a flag or a type it doesn't know. Every new record
|
||||
* type is raised in coredump_req->mask as a feature of its own. A
|
||||
* server only ever sees the types it asked for.
|
||||
*
|
||||
* COREDUMP_RECORDS must be combined with COREDUMP_KERNEL, and
|
||||
* COREDUMP_SPARSE with COREDUMP_RECORDS.
|
||||
*/
|
||||
struct coredump_record_header {
|
||||
__u32 size;
|
||||
__u32 type;
|
||||
__u64 flags;
|
||||
__u64 offset;
|
||||
__u64 len;
|
||||
};
|
||||
|
||||
enum {
|
||||
COREDUMP_RECORD_HEADER_SIZE_VER0 = 32U, /* size of first published struct */
|
||||
};
|
||||
|
||||
#endif /* _UAPI_LINUX_COREDUMP_H */
|
||||
|
|
|
|||
|
|
@ -11,12 +11,19 @@
|
|||
* @COREDUMP_USERSPACE: userspace writes coredump
|
||||
* @COREDUMP_REJECT: don't generate coredump
|
||||
* @COREDUMP_WAIT: wait for coredump server
|
||||
* @COREDUMP_RECORDS: send the coredump as a sequence of records instead of
|
||||
* as a plain byte stream, see struct coredump_record_header;
|
||||
* requires COREDUMP_KERNEL
|
||||
* @COREDUMP_SPARSE: describe the holes in the coredump as zero records
|
||||
* instead of transferring them; requires COREDUMP_RECORDS
|
||||
*/
|
||||
enum {
|
||||
COREDUMP_KERNEL = (1ULL << 0),
|
||||
COREDUMP_USERSPACE = (1ULL << 1),
|
||||
COREDUMP_REJECT = (1ULL << 2),
|
||||
COREDUMP_WAIT = (1ULL << 3),
|
||||
COREDUMP_RECORDS = (1ULL << 4),
|
||||
COREDUMP_SPARSE = (1ULL << 5),
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -30,11 +37,11 @@ enum {
|
|||
* member is set to the size of struct coredump_req and provides a hint
|
||||
* to userspace how much data can be read. Userspace may use MSG_PEEK to
|
||||
* peek the size of struct coredump_req and then choose to consume it in
|
||||
* one go. Userspace may also simply read a COREDUMP_ACK_SIZE_VER0
|
||||
* one go. Userspace may also simply read a COREDUMP_REQ_SIZE_VER0
|
||||
* request. If the size the kernel sends is larger userspace simply
|
||||
* discards any remaining data.
|
||||
*
|
||||
* The coredump_req->mask member is set to the currently know features.
|
||||
* The coredump_req->mask member is set to the currently known features.
|
||||
* Userspace may only set coredump_ack->mask to the bits raised by the
|
||||
* kernel in coredump_req->mask.
|
||||
*
|
||||
|
|
@ -101,4 +108,72 @@ enum coredump_mark {
|
|||
__COREDUMP_MARK_MAX = (1U << 31),
|
||||
};
|
||||
|
||||
/**
|
||||
* enum coredump_record_type - Type of a coredump record
|
||||
*
|
||||
* @COREDUMP_RECORD_DATA: the header is followed by ->len bytes of data
|
||||
* @COREDUMP_RECORD_END: the coredump ends here, the header is not followed
|
||||
* by any data and no further record is sent
|
||||
* @COREDUMP_RECORD_ZERO: the header stands for ->len zero bytes and is not
|
||||
* followed by any data
|
||||
* @__COREDUMP_RECORD_TYPE_MAX: the maximum coredump record type value
|
||||
*/
|
||||
enum coredump_record_type {
|
||||
COREDUMP_RECORD_DATA = 0U,
|
||||
COREDUMP_RECORD_END = 1U,
|
||||
COREDUMP_RECORD_ZERO = 2U,
|
||||
__COREDUMP_RECORD_TYPE_MAX = (1U << 31),
|
||||
};
|
||||
|
||||
/**
|
||||
* struct coredump_record_header - header of a coredump record
|
||||
* @size: size of struct coredump_record_header
|
||||
* @type: one of enum coredump_record_type
|
||||
* @flags: modifiers for this record
|
||||
* @offset: offset in the coredump this record starts at
|
||||
* @len: number of coredump bytes this record accounts for
|
||||
*
|
||||
* If the coredump server raises COREDUMP_RECORDS in coredump_ack->mask
|
||||
* the kernel doesn't send the coredump as a plain byte stream. It sends
|
||||
* a sequence of records instead. A COREDUMP_RECORD_DATA record is
|
||||
* followed by @len bytes of actual coredump data. A
|
||||
* COREDUMP_RECORD_ZERO record is followed by nothing and stands for
|
||||
* @len zero bytes. A server that didn't raise COREDUMP_SPARSE never
|
||||
* sees a zero record. Records arrive in order and leave no gaps. So
|
||||
* @offset is the sum of the @len of all records before it.
|
||||
*
|
||||
* The last record is a COREDUMP_RECORD_END record. It is followed by
|
||||
* nothing. Its @len is zero. Its @offset is the size of the coredump.
|
||||
* The kernel only sends it once it has written the whole coredump. A
|
||||
* server that hits end-of-file without having seen an end record must
|
||||
* treat the coredump as incomplete.
|
||||
*
|
||||
* The @size member is set to the size of struct coredump_record_header
|
||||
* the kernel knows and lets the header grow later. It comes first so it
|
||||
* can be peeked. Userspace must consume @size bytes and discard
|
||||
* anything beyond what it knows. It must refuse a @size smaller than
|
||||
* COREDUMP_RECORD_HEADER_SIZE_VER0. @size covers the header alone.
|
||||
* @offset and @len count coredump bytes.
|
||||
*
|
||||
* The @flags member carries modifiers that change how the record is to
|
||||
* be interpreted. No flag is defined yet. Userspace must refuse a
|
||||
* record carrying a flag or a type it doesn't know. Every new record
|
||||
* type is raised in coredump_req->mask as a feature of its own. A
|
||||
* server only ever sees the types it asked for.
|
||||
*
|
||||
* COREDUMP_RECORDS must be combined with COREDUMP_KERNEL, and
|
||||
* COREDUMP_SPARSE with COREDUMP_RECORDS.
|
||||
*/
|
||||
struct coredump_record_header {
|
||||
__u32 size;
|
||||
__u32 type;
|
||||
__u64 flags;
|
||||
__u64 offset;
|
||||
__u64 len;
|
||||
};
|
||||
|
||||
enum {
|
||||
COREDUMP_RECORD_HEADER_SIZE_VER0 = 32U, /* size of first published struct */
|
||||
};
|
||||
|
||||
#endif /* _UAPI_LINUX_COREDUMP_H */
|
||||
|
|
|
|||
|
|
@ -151,9 +151,7 @@ TEST_F(coredump, socket_request_kernel)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "socket_request_kernel: check_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -301,9 +299,7 @@ TEST_F(coredump, socket_request_userspace)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "socket_request_userspace: check_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -441,9 +437,7 @@ TEST_F(coredump, socket_request_reject)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "socket_request_reject: check_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -581,9 +575,7 @@ TEST_F(coredump, socket_request_invalid_flag_combination)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "socket_request_invalid_flag_combination: check_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -702,9 +694,7 @@ TEST_F(coredump, socket_request_unknown_flag)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "socket_request_unknown_flag: check_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -822,9 +812,7 @@ TEST_F(coredump, socket_request_invalid_size_small)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "socket_request_invalid_size_small: check_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -944,9 +932,7 @@ TEST_F(coredump, socket_request_invalid_size_large)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "socket_request_invalid_size_large: check_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -1355,9 +1341,7 @@ TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps, 500)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "check_coredump_req failed for fd %d\n", fd_coredump);
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -1509,9 +1493,7 @@ TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps_epoll_workers, 500)
|
|||
fprintf(stderr, "socket_multiple_crashing_coredumps_epoll_workers: read_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0,
|
||||
COREDUMP_KERNEL | COREDUMP_USERSPACE |
|
||||
COREDUMP_REJECT | COREDUMP_WAIT)) {
|
||||
if (!check_coredump_req(&req)) {
|
||||
fprintf(stderr, "socket_multiple_crashing_coredumps_epoll_workers: check_coredump_req failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -1591,4 +1573,751 @@ TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps_epoll_workers, 500)
|
|||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reassemble a record stream and check that what comes out is an ELF
|
||||
* core file. The records themselves are validated by recv_coredump_records().
|
||||
*/
|
||||
TEST_F(coredump, socket_request_sparse_reassemble)
|
||||
{
|
||||
int fd_core_file, pidfd, status;
|
||||
pid_t pid, pid_coredump_server;
|
||||
struct pidfd_info info = {};
|
||||
int ipc_sockets[2];
|
||||
char c;
|
||||
|
||||
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
|
||||
ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
|
||||
|
||||
pid_coredump_server = fork();
|
||||
ASSERT_GE(pid_coredump_server, 0);
|
||||
if (pid_coredump_server == 0) {
|
||||
int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
|
||||
int fd_file = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
struct coredump_req req = {};
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
|
||||
if (fd_server < 0)
|
||||
goto out;
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
|
||||
goto out;
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0)
|
||||
goto out;
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0)
|
||||
goto out;
|
||||
|
||||
fd_file = creat("/tmp/coredump.file", 0644);
|
||||
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,
|
||||
COREDUMP_KERNEL | COREDUMP_RECORDS |
|
||||
COREDUMP_SPARSE | COREDUMP_WAIT, 0))
|
||||
goto out;
|
||||
|
||||
if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK))
|
||||
goto out;
|
||||
|
||||
if (recv_coredump_records(fd_coredump, fd_file, NULL, NULL, -1) < 0)
|
||||
goto out;
|
||||
|
||||
exit_code = EXIT_SUCCESS;
|
||||
out:
|
||||
if (fd_file >= 0)
|
||||
close(fd_file);
|
||||
if (fd_peer_pidfd >= 0)
|
||||
close(fd_peer_pidfd);
|
||||
if (fd_coredump >= 0)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
self->pid_coredump_server = pid_coredump_server;
|
||||
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
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_TRUE(WCOREDUMP(status));
|
||||
|
||||
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
|
||||
ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
|
||||
ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
|
||||
|
||||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
|
||||
/* What the records reassemble into has to be an ELF core file. */
|
||||
fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC);
|
||||
ASSERT_GE(fd_core_file, 0);
|
||||
ASSERT_TRUE(is_elf_core(fd_core_file));
|
||||
EXPECT_EQ(close(fd_core_file), 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Crash a child with a mostly-unpopulated mapping and reassemble its
|
||||
* record stream, reporting what crossed the socket and the coredump
|
||||
* size the records describe. With @kill_peer the server kills the task
|
||||
* once the coredump is under way so the kernel has to cut it short.
|
||||
*/
|
||||
static void check_record_dump(struct __test_metadata *const _metadata,
|
||||
FIXTURE_DATA(coredump) *self, __u64 ack_mask,
|
||||
bool kill_peer, ssize_t *received,
|
||||
off_t *coredump_size)
|
||||
{
|
||||
bool truncated = false;
|
||||
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 = {};
|
||||
bool is_truncated = false;
|
||||
off_t size = 0;
|
||||
ssize_t ret;
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
close(pipefds[0]);
|
||||
|
||||
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
|
||||
if (fd_server < 0)
|
||||
goto out;
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
|
||||
goto out;
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0)
|
||||
goto out;
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* 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))
|
||||
goto out;
|
||||
|
||||
if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK))
|
||||
goto out;
|
||||
|
||||
ret = recv_coredump_records(fd_coredump, fd_file, &size, &is_truncated,
|
||||
kill_peer ? fd_peer_pidfd : -1);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (write_nointr(pipefds[1], &ret, sizeof(ret)) != sizeof(ret))
|
||||
goto out;
|
||||
if (write_nointr(pipefds[1], &size, sizeof(size)) != sizeof(size))
|
||||
goto out;
|
||||
if (write_nointr(pipefds[1], &is_truncated,
|
||||
sizeof(is_truncated)) != sizeof(is_truncated))
|
||||
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)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
self->pid_coredump_server = pid_coredump_server;
|
||||
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
EXPECT_EQ(close(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_sparse(SPARSE_MAPPING_SIZE);
|
||||
|
||||
pidfd = sys_pidfd_open(pid, 0);
|
||||
ASSERT_GE(pidfd, 0);
|
||||
|
||||
waitpid(pid, &status, 0);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
|
||||
ASSERT_EQ(read_nointr(pipefds[0], received, sizeof(*received)),
|
||||
sizeof(*received));
|
||||
ASSERT_EQ(read_nointr(pipefds[0], coredump_size, sizeof(*coredump_size)),
|
||||
sizeof(*coredump_size));
|
||||
ASSERT_EQ(read_nointr(pipefds[0], &truncated, sizeof(truncated)),
|
||||
sizeof(truncated));
|
||||
EXPECT_EQ(close(pipefds[0]), 0);
|
||||
|
||||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
|
||||
if (kill_peer) {
|
||||
/* The kernel gave up partway, so no end record closed the stream. */
|
||||
ASSERT_TRUE(truncated);
|
||||
ASSERT_FALSE(WCOREDUMP(status));
|
||||
ASSERT_LT(*coredump_size, (off_t)SPARSE_MAPPING_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_FALSE(truncated);
|
||||
ASSERT_TRUE(WCOREDUMP(status));
|
||||
|
||||
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
|
||||
ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
|
||||
ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
|
||||
|
||||
/* The mapping is in the coredump, holes included. */
|
||||
ASSERT_GT(*coredump_size, (off_t)SPARSE_MAPPING_SIZE);
|
||||
}
|
||||
|
||||
/*
|
||||
* A mapping that has been written to is dumped whole, including the parts
|
||||
* of it that were never faulted in. With COREDUMP_SPARSE the holes stay
|
||||
* off the wire.
|
||||
*/
|
||||
TEST_F(coredump, socket_request_sparse_hole)
|
||||
{
|
||||
off_t coredump_size = 0;
|
||||
ssize_t received = 0;
|
||||
|
||||
check_record_dump(_metadata, self,
|
||||
COREDUMP_KERNEL | COREDUMP_RECORDS |
|
||||
COREDUMP_SPARSE | COREDUMP_WAIT,
|
||||
false, &received, &coredump_size);
|
||||
|
||||
/* The holes didn't have to go over the socket. */
|
||||
ASSERT_LT(received, coredump_size / 8);
|
||||
}
|
||||
|
||||
/*
|
||||
* COREDUMP_RECORDS alone splits the stream into records but elides
|
||||
* nothing: the holes cross the socket as data records.
|
||||
*/
|
||||
TEST_F(coredump, socket_request_records_hole)
|
||||
{
|
||||
off_t coredump_size = 0;
|
||||
ssize_t received = 0;
|
||||
|
||||
check_record_dump(_metadata, self,
|
||||
COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_WAIT,
|
||||
false, &received, &coredump_size);
|
||||
|
||||
/* Records alone elide nothing, so everything crossed the socket. */
|
||||
ASSERT_GT(received, coredump_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* A coredump the kernel gives up on halfway still ends in an end record,
|
||||
* and that record says the coredump is incomplete. COREDUMP_SPARSE is left
|
||||
* out on purpose: the holes have to cross the socket so the coredump is
|
||||
* far larger than the socket buffer and the kernel is still writing it
|
||||
* when the kill lands.
|
||||
*/
|
||||
TEST_F(coredump, socket_request_records_truncated)
|
||||
{
|
||||
off_t coredump_size = 0;
|
||||
ssize_t received = 0;
|
||||
|
||||
check_record_dump(_metadata, self,
|
||||
COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_WAIT,
|
||||
true, &received, &coredump_size);
|
||||
|
||||
/* The end record crossed the socket even though the task was killed. */
|
||||
ASSERT_GT(received, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* A coredump server that uploads to a blob store can't upload a sparse
|
||||
* file. It doesn't have to: it streams the data records into the object
|
||||
* as they arrive, leaves the holes out, and uploads the corrected
|
||||
* program header table last. What it ends up with is an ordinary ELF
|
||||
* core file that describes the same memory as the coredump the records
|
||||
* came from, minus the holes.
|
||||
*/
|
||||
TEST_F(coredump, socket_request_sparse_blob_upload)
|
||||
{
|
||||
int fd_core_file, pidfd, status;
|
||||
pid_t pid, pid_coredump_server;
|
||||
struct pidfd_info info = {};
|
||||
off_t coredump_size = 0;
|
||||
ssize_t received = 0;
|
||||
int ipc_sockets[2];
|
||||
int pipefds[2];
|
||||
struct stat st;
|
||||
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_object = -1, fd_reference = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
struct coredump_req req = {};
|
||||
off_t size = 0;
|
||||
ssize_t ret;
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
close(pipefds[0]);
|
||||
|
||||
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
|
||||
if (fd_server < 0)
|
||||
goto out;
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
|
||||
goto out;
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0)
|
||||
goto out;
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0)
|
||||
goto out;
|
||||
|
||||
/* The object is a plain file. It never sees a hole. */
|
||||
fd_object = open("/tmp/coredump.file",
|
||||
O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600);
|
||||
if (fd_object < 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* The coredump with its holes still in it is bigger than
|
||||
* the mapping the child made, so keep it on the detached
|
||||
* tmpfs and sparse.
|
||||
*/
|
||||
fd_reference = open_coredump_tmpfile(self->fd_tmpfs_detached);
|
||||
if (fd_reference < 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,
|
||||
COREDUMP_KERNEL | COREDUMP_RECORDS |
|
||||
COREDUMP_SPARSE | COREDUMP_WAIT, 0))
|
||||
goto out;
|
||||
|
||||
if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK))
|
||||
goto out;
|
||||
|
||||
ret = recv_coredump_compact(fd_coredump, fd_object,
|
||||
fd_reference, &size);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (check_compact_coredump(fd_object, fd_reference))
|
||||
goto out;
|
||||
|
||||
if (write_nointr(pipefds[1], &ret, sizeof(ret)) != sizeof(ret))
|
||||
goto out;
|
||||
if (write_nointr(pipefds[1], &size, sizeof(size)) != sizeof(size))
|
||||
goto out;
|
||||
|
||||
exit_code = EXIT_SUCCESS;
|
||||
out:
|
||||
close(pipefds[1]);
|
||||
if (fd_reference >= 0)
|
||||
close(fd_reference);
|
||||
if (fd_object >= 0)
|
||||
close(fd_object);
|
||||
if (fd_peer_pidfd >= 0)
|
||||
close(fd_peer_pidfd);
|
||||
if (fd_coredump >= 0)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
self->pid_coredump_server = pid_coredump_server;
|
||||
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
EXPECT_EQ(close(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_sparse(SPARSE_MAPPING_SIZE);
|
||||
|
||||
pidfd = sys_pidfd_open(pid, 0);
|
||||
ASSERT_GE(pidfd, 0);
|
||||
|
||||
waitpid(pid, &status, 0);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_TRUE(WCOREDUMP(status));
|
||||
|
||||
ASSERT_EQ(read_nointr(pipefds[0], &received, sizeof(received)),
|
||||
sizeof(received));
|
||||
ASSERT_EQ(read_nointr(pipefds[0], &coredump_size, sizeof(coredump_size)),
|
||||
sizeof(coredump_size));
|
||||
EXPECT_EQ(close(pipefds[0]), 0);
|
||||
|
||||
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
|
||||
ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
|
||||
ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
|
||||
|
||||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
|
||||
/* The mapping is in the coredump, holes included. */
|
||||
ASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE);
|
||||
|
||||
/* The object isn't sparse and doesn't carry them. */
|
||||
ASSERT_EQ(stat("/tmp/coredump.file", &st), 0);
|
||||
ASSERT_LT(st.st_size, coredump_size / 8);
|
||||
|
||||
/* And a debugger still sees an ordinary ELF core file. */
|
||||
fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC);
|
||||
ASSERT_GE(fd_core_file, 0);
|
||||
ASSERT_TRUE(is_elf_core(fd_core_file));
|
||||
EXPECT_EQ(close(fd_core_file), 0);
|
||||
}
|
||||
|
||||
/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */
|
||||
static void check_conflicting_ack(struct __test_metadata *const _metadata,
|
||||
FIXTURE_DATA(coredump) *self, __u64 ack_mask)
|
||||
{
|
||||
int pidfd, status;
|
||||
pid_t pid, pid_coredump_server;
|
||||
struct pidfd_info info = {};
|
||||
int ipc_sockets[2];
|
||||
char c;
|
||||
|
||||
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
|
||||
ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket"));
|
||||
|
||||
pid_coredump_server = fork();
|
||||
ASSERT_GE(pid_coredump_server, 0);
|
||||
if (pid_coredump_server == 0) {
|
||||
int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
struct coredump_req req = {};
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
|
||||
if (fd_server < 0)
|
||||
goto out;
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
|
||||
goto out;
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0)
|
||||
goto out;
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0)
|
||||
goto out;
|
||||
|
||||
if (!read_coredump_req(fd_coredump, &req))
|
||||
goto out;
|
||||
|
||||
if (!check_coredump_req(&req))
|
||||
goto out;
|
||||
|
||||
if (!send_coredump_ack(fd_coredump, &req, ack_mask, 0))
|
||||
goto out;
|
||||
|
||||
if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING))
|
||||
goto out;
|
||||
|
||||
exit_code = EXIT_SUCCESS;
|
||||
out:
|
||||
if (fd_peer_pidfd >= 0)
|
||||
close(fd_peer_pidfd);
|
||||
if (fd_coredump >= 0)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
self->pid_coredump_server = pid_coredump_server;
|
||||
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
|
||||
EXPECT_EQ(close(ipc_sockets[0]), 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
if (pid == 0)
|
||||
crashing_child();
|
||||
|
||||
pidfd = sys_pidfd_open(pid, 0);
|
||||
ASSERT_GE(pidfd, 0);
|
||||
|
||||
waitpid(pid, &status, 0);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_FALSE(WCOREDUMP(status));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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)
|
||||
goto out;
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
|
||||
goto out;
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0)
|
||||
goto out;
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/* 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)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
self->pid_coredump_server = pid_coredump_server;
|
||||
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
EXPECT_EQ(close(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_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_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);
|
||||
|
||||
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)
|
||||
{
|
||||
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 task sitting on a big mapping is. */
|
||||
TEST_F(coredump, socket_request_stream_choice_large)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
|
|
@ -3,18 +3,9 @@
|
|||
#ifndef __COREDUMP_TEST_H
|
||||
#define __COREDUMP_TEST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <linux/coredump.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
#include "../pidfd/pidfd.h"
|
||||
|
||||
#ifndef PAGE_SIZE
|
||||
#define PAGE_SIZE 4096
|
||||
#endif
|
||||
|
||||
#define NUM_THREAD_SPAWN 128
|
||||
#include "coredump_test_helpers.h"
|
||||
|
||||
/* Coredump fixture */
|
||||
FIXTURE(coredump)
|
||||
|
|
@ -24,15 +15,6 @@ FIXTURE(coredump)
|
|||
int fd_tmpfs_detached;
|
||||
};
|
||||
|
||||
/* Shared helper function declarations */
|
||||
void *do_nothing(void *arg);
|
||||
void crashing_child(void);
|
||||
int create_detached_tmpfs(void);
|
||||
int create_and_listen_unix_socket(const char *path);
|
||||
bool set_core_pattern(const char *pattern);
|
||||
int get_peer_pidfd(int fd);
|
||||
bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);
|
||||
|
||||
/* Inline helper that uses harness types */
|
||||
static inline void wait_and_check_coredump_server(pid_t pid_coredump_server,
|
||||
struct __test_metadata *const _metadata,
|
||||
|
|
@ -45,15 +27,4 @@ static inline void wait_and_check_coredump_server(pid_t pid_coredump_server,
|
|||
ASSERT_EQ(WEXITSTATUS(status), 0);
|
||||
}
|
||||
|
||||
/* Protocol helper function declarations */
|
||||
ssize_t recv_marker(int fd);
|
||||
bool read_marker(int fd, enum coredump_mark mark);
|
||||
bool read_coredump_req(int fd, struct coredump_req *req);
|
||||
bool send_coredump_ack(int fd, const struct coredump_req *req,
|
||||
__u64 mask, size_t size_ack);
|
||||
bool check_coredump_req(const struct coredump_req *req, size_t min_size,
|
||||
__u64 required_mask);
|
||||
int open_coredump_tmpfile(int fd_tmpfs_detached);
|
||||
void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file);
|
||||
|
||||
#endif /* __COREDUMP_TEST_H */
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
53
tools/testing/selftests/coredump/coredump_test_helpers.h
Normal file
53
tools/testing/selftests/coredump/coredump_test_helpers.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#ifndef __COREDUMP_TEST_HELPERS_H
|
||||
#define __COREDUMP_TEST_HELPERS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <linux/coredump.h>
|
||||
|
||||
#include "../pidfd/pidfd.h"
|
||||
|
||||
#ifndef PAGE_SIZE
|
||||
#define PAGE_SIZE 4096
|
||||
#endif
|
||||
|
||||
#define NUM_THREAD_SPAWN 128
|
||||
|
||||
/* Size of the mostly unpopulated mapping the sparse coredump test maps. */
|
||||
#define SPARSE_MAPPING_SIZE (256 * 1024 * 1024)
|
||||
|
||||
/* A task mapping at least this much is worth a record stream. */
|
||||
#define SPARSE_STREAM_THRESHOLD (SPARSE_MAPPING_SIZE / 2)
|
||||
|
||||
/* Shared helper function declarations */
|
||||
void *do_nothing(void *arg);
|
||||
void crashing_child(void);
|
||||
void crashing_child_sparse(size_t size);
|
||||
ssize_t recv_coredump_records(int fd_coredump, int fd_core_file,
|
||||
off_t *coredump_size, bool *truncated,
|
||||
int fd_peer_pidfd);
|
||||
ssize_t recv_coredump_compact(int fd_coredump, int fd_object, int fd_reference,
|
||||
off_t *coredump_size);
|
||||
ssize_t recv_coredump_bytes(int fd_coredump, int fd_core_file);
|
||||
ssize_t peer_vm_size(int fd_peer_pidfd);
|
||||
bool is_elf_core(int fd);
|
||||
int check_compact_coredump(int fd_object, int fd_reference);
|
||||
int create_detached_tmpfs(void);
|
||||
int create_and_listen_unix_socket(const char *path);
|
||||
bool set_core_pattern(const char *pattern);
|
||||
int get_peer_pidfd(int fd);
|
||||
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_coredump_req(int fd, struct coredump_req *req);
|
||||
bool send_coredump_ack(int fd, const struct coredump_req *req,
|
||||
__u64 mask, size_t size_ack);
|
||||
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);
|
||||
|
||||
#endif /* __COREDUMP_TEST_HELPERS_H */
|
||||
Loading…
Reference in New Issue
Block a user