Improve the size handling tests when negotiating a coredump through req and ack.
Signed-off-by: Christian Brauner (Amutable) <[email protected]> --- .../coredump/coredump_socket_protocol_test.c | 158 +++++++++++++++++++-- .../selftests/coredump/coredump_test_helpers.c | 17 ++- .../selftests/coredump/coredump_test_helpers.h | 1 + 3 files changed, 155 insertions(+), 21 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index 6dcd6c15a565..6c7327832d44 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -1932,11 +1932,74 @@ TEST_F(coredump, socket_request_stream_choice_large) ASSERT_LT(choice.received, choice.size / 8); } +/* What a coredump server was built with. */ +struct server_build { + /* sizeof(struct coredump_req) and sizeof(struct coredump_ack) back then. */ + size_t req_size; + size_t ack_size; + /* The features it raises if the kernel offers them. */ + __u64 wants; + /* Its policy: what it drops from and adds to the task's selection. */ + __u64 drop; + __u64 add; +}; + +/* A server from when the structs were first published: kernel-written dumps. */ +static const struct server_build server_build_ver0 = { + .req_size = COREDUMP_REQ_SIZE_VER0, + .ack_size = COREDUMP_ACK_SIZE_VER0, + .wants = COREDUMP_KERNEL, +}; + +/* A server built against this header: no shared memory, always the ELF headers. */ +static const struct server_build server_build_ver1 = { + .req_size = sizeof(struct coredump_req), + .ack_size = sizeof(struct coredump_ack), + .wants = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES, + .drop = COREDUMP_MEMORY_ANON_SHARED | COREDUMP_MEMORY_FILE_SHARED, + .add = COREDUMP_MEMORY_ELF_HEADERS, +}; + +/* + * Build the ack the way a server does: from what the kernel offers, what + * this build implements, and what fits in the ack the kernel accepts. + * Fields the build never read are zero and never consulted. + */ +static void negotiate(const struct coredump_req *req, + const struct server_build *build, + struct coredump_ack *ack) +{ + __u64 offered = req->mask & build->wants; + + memset(ack, 0, sizeof(*ack)); + ack->size = build->ack_size < req->size_ack ? build->ack_size : req->size_ack; + /* These builds only ever have the kernel write the coredump. */ + ack->mask = COREDUMP_KERNEL; + + /* Sparse needs records, records need the kernel to write. */ + if (offered & COREDUMP_RECORDS) { + ack->mask |= COREDUMP_RECORDS; + if (offered & COREDUMP_SPARSE) + ack->mask |= COREDUMP_SPARSE; + } + + /* The memory types need an ack that carries them. */ + if ((offered & COREDUMP_MEMORY_TYPES) && ack->size >= COREDUMP_ACK_SIZE_VER1) { + ack->mask |= COREDUMP_MEMORY_TYPES; + /* Start from the task's selection; only advertised types pass. */ + ack->memory_types = (req->memory_types & ~build->drop) | build->add; + ack->memory_types &= req->memory_types_mask; + } +} + /* What a memory types test asks of the kernel and what it expects back. */ struct memory_choice { /* Memory types the crashing child selects, or FILTER_TASK_INHERIT. */ __u64 task_filter; - /* The ack. */ + /* Negotiate the ack as this server build, NULL to send it as given. */ + const struct server_build *build; + /* The ack, or what the negotiation must arrive at. */ __u64 mask; __u64 memory_types; size_t size_ack; @@ -1976,6 +2039,13 @@ static void check_memory_dump(struct __test_metadata *const _metadata, int fd_file = -1; int exit_code = EXIT_FAILURE; struct coredump_req req = {}; + struct coredump_ack ack = { + .size = choice->size_ack, + .mask = choice->mask, + .memory_types = choice->memory_types, + }; + /* How much of the request this server reads. */ + size_t req_size = choice->build ? choice->build->req_size : sizeof(req); __u64 task_filter; ElfW(Phdr) segment; ssize_t received; @@ -2006,21 +2076,24 @@ static void check_memory_dump(struct __test_metadata *const _metadata, if (fd_file < 0) goto out; - if (!read_coredump_req(fd_coredump, &req)) - goto out; - - if (!check_coredump_req(&req)) + if (!read_coredump_req_sized(fd_coredump, &req, req_size)) goto out; - /* The request reports the memory types the task selected. */ if (!peer_coredump_filter(fd_peer_pidfd, &task_filter)) goto out; - if (req.memory_types != task_filter) { - fprintf(stderr, "Request reports 0x%llx, task selected 0x%llx\n", - (unsigned long long)req.memory_types, - (unsigned long long)task_filter); - goto out; + /* A build from before the memory types never read that far. */ + if (req_size >= COREDUMP_REQ_SIZE_VER1) { + if (!check_coredump_req(&req)) + goto out; + + /* The request reports the memory types the task selected. */ + if (req.memory_types != task_filter) { + fprintf(stderr, "Request reports 0x%llx, task selected 0x%llx\n", + (unsigned long long)req.memory_types, + (unsigned long long)task_filter); + goto out; + } } if (choice->task_filter != FILTER_TASK_INHERIT && @@ -2035,15 +2108,28 @@ static void check_memory_dump(struct __test_metadata *const _metadata, if (read_nointr(addr_pipe[0], &addr, sizeof(addr)) != sizeof(addr)) goto out; - if (!send_coredump_ack_types(fd_coredump, &req, choice->mask, - choice->memory_types, - choice->size_ack)) + /* A server build negotiates its ack and must arrive at the choice. */ + if (choice->build) { + negotiate(&req, choice->build, &ack); + + if (ack.size != choice->size_ack || ack.mask != choice->mask || + ack.memory_types != choice->memory_types) { + fprintf(stderr, + "Negotiated %u bytes, mask 0x%llx, types 0x%llx\n", + ack.size, (unsigned long long)ack.mask, + (unsigned long long)ack.memory_types); + goto out; + } + } + + if (!send_coredump_ack_types(fd_coredump, &req, ack.mask, + ack.memory_types, ack.size)) goto out; if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) goto out; - if (choice->mask & COREDUMP_RECORDS) + if (ack.mask & COREDUMP_RECORDS) received = recv_coredump_records(fd_coredump, fd_file, &size, NULL, -1); else @@ -2281,4 +2367,46 @@ TEST_F(coredump, socket_request_memory_types_without_kernel) check_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_MEMORY_TYPES); } +/* + * A server built with the first structs reads the request it knows, + * discards the rest and acks with the ack it knows. It raises nothing + * it wasn't built for and the kernel dumps what the task selected. + */ +TEST_F(coredump, socket_request_negotiate_ver0) +{ + struct memory_choice choice = { + .task_filter = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .build = &server_build_ver0, + .mask = COREDUMP_KERNEL, + .memory_types = 0, + .size_ack = COREDUMP_ACK_SIZE_VER0, + .shared_dumped = true, + }; + + check_memory_dump(_metadata, self, &choice); +} + +/* + * A server built against this header takes every feature the kernel + * offers, drops shared memory from what the task selected and adds the + * ELF headers. + */ +TEST_F(coredump, socket_request_negotiate_ver1) +{ + struct memory_choice choice = { + .task_filter = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .build = &server_build_ver1, + .mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES, + .memory_types = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ELF_HEADERS, + .size_ack = COREDUMP_ACK_SIZE_VER1, + .shared_dumped = false, + }; + + check_memory_dump(_metadata, self, &choice); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 7ae0c6c458aa..ab94c45cd8be 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -1467,10 +1467,11 @@ bool read_marker(int fd, enum coredump_mark mark) return ret == mark; } -bool read_coredump_req(int fd, struct coredump_req *req) +/* 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, user_size, known_size, kernel_size, remaining_size; + size_t field_size, known_size, kernel_size, remaining_size; memset(req, 0, sizeof(*req)); field_size = sizeof(req->size); @@ -1478,25 +1479,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) @@ -1529,6 +1529,11 @@ bool read_coredump_req(int fd, struct coredump_req *req) return true; } +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) { diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h index fc21b8620359..8e0187645c93 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.h +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -65,6 +65,7 @@ bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info); 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 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, -- 2.53.0

