strace/dm.c

609 lines
13 KiB
C
Raw Normal View History

2016-12-26 15:24:45 +03:00
/*
* Support for decoding of DM_* ioctl commands.
*
* Copyright (c) 2016 Mikulas Patocka <mpatocka@redhat.com>
* Copyright (c) 2016 Masatake Yamato <yamato@redhat.com>
* Copyright (c) 2016 Dmitry V. Levin <ldv@altlinux.org>
* Copyright (c) 2016 Eugene Syromyatnikov <evgsyr@gmail.com>
* Copyright (c) 2016-2018 The strace developers.
2016-12-26 15:24:45 +03:00
* All rights reserved.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
2016-12-26 15:24:45 +03:00
*/
#include "defs.h"
#ifdef HAVE_LINUX_DM_IOCTL_H
# include "print_fields.h"
# include <linux/dm-ioctl.h>
# include <linux/ioctl.h>
# if DM_VERSION_MAJOR == 4
/* Definitions for command which have been added later */
# ifndef DM_LIST_VERSIONS
# define DM_LIST_VERSIONS _IOWR(DM_IOCTL, 0x0d, struct dm_ioctl)
# endif
# ifndef DM_TARGET_MSG
# define DM_TARGET_MSG _IOWR(DM_IOCTL, 0x0e, struct dm_ioctl)
# endif
# ifndef DM_DEV_SET_GEOMETRY
# define DM_DEV_SET_GEOMETRY _IOWR(DM_IOCTL, 0x0f, struct dm_ioctl)
# endif
# ifndef DM_DEV_ARM_POLL
# define DM_DEV_ARM_POLL _IOWR(DM_IOCTL, 0x10, struct dm_ioctl)
# endif
static void
dm_decode_device(const unsigned int code, const struct dm_ioctl *ioc)
{
switch (code) {
case DM_REMOVE_ALL:
case DM_LIST_DEVICES:
case DM_LIST_VERSIONS:
break;
default:
if (ioc->dev)
PRINT_FIELD_DEV(", ", *ioc, dev);
if (ioc->name[0])
PRINT_FIELD_CSTRING(", ", *ioc, name);
if (ioc->uuid[0])
PRINT_FIELD_CSTRING(", ", *ioc, uuid);
break;
}
}
static void
dm_decode_values(struct tcb *tcp, const unsigned int code,
const struct dm_ioctl *ioc)
{
if (entering(tcp)) {
switch (code) {
case DM_TABLE_LOAD:
PRINT_FIELD_U(", ", *ioc, target_count);
break;
case DM_DEV_SUSPEND:
if (ioc->flags & DM_SUSPEND_FLAG)
break;
ATTRIBUTE_FALLTHROUGH;
case DM_DEV_RENAME:
case DM_DEV_REMOVE:
case DM_DEV_WAIT:
PRINT_FIELD_U(", ", *ioc, event_nr);
break;
}
} else if (!syserror(tcp)) {
switch (code) {
case DM_DEV_CREATE:
case DM_DEV_RENAME:
case DM_DEV_SUSPEND:
case DM_DEV_STATUS:
case DM_DEV_WAIT:
case DM_TABLE_LOAD:
case DM_TABLE_CLEAR:
case DM_TABLE_DEPS:
case DM_TABLE_STATUS:
case DM_TARGET_MSG:
PRINT_FIELD_U(", ", *ioc, target_count);
PRINT_FIELD_U(", ", *ioc, open_count);
PRINT_FIELD_U(", ", *ioc, event_nr);
break;
}
}
}
# include "xlat/dm_flags.h"
static void
dm_decode_flags(const struct dm_ioctl *ioc)
{
PRINT_FIELD_FLAGS(", ", *ioc, flags, dm_flags, "DM_???");
}
static void
dm_decode_dm_target_spec(struct tcb *const tcp, const kernel_ulong_t addr,
const struct dm_ioctl *const ioc)
{
static const uint32_t target_spec_size =
sizeof(struct dm_target_spec);
uint32_t i;
uint32_t offset = ioc->data_start;
uint32_t offset_end = 0;
if (abbrev(tcp)) {
if (ioc->target_count)
tprints(", ...");
return;
}
for (i = 0; i < ioc->target_count; i++) {
tprints(", ");
if (i && offset <= offset_end)
goto misplaced;
offset_end = offset + target_spec_size;
if (offset_end <= offset || offset_end > ioc->data_size)
goto misplaced;
if (i >= max_strlen) {
tprints("...");
break;
}
struct dm_target_spec s;
if (umove_or_printaddr(tcp, addr + offset, &s))
break;
PRINT_FIELD_U("{", s, sector_start);
PRINT_FIELD_U(", ", s, length);
if (exiting(tcp))
PRINT_FIELD_D(", ", s, status);
PRINT_FIELD_CSTRING(", ", s, target_type);
tprints(", string=");
printstr_ex(tcp, addr + offset_end, ioc->data_size - offset_end,
QUOTE_0_TERMINATED);
tprints("}");
if (entering(tcp))
offset += s.next;
else
offset = ioc->data_start + s.next;
}
return;
misplaced:
tprints("???");
tprints_comment("misplaced struct dm_target_spec");
}
bool
dm_print_dev(struct tcb *tcp, void *dev_ptr, size_t dev_size, void *dummy)
{
uint64_t *dev = (uint64_t *) dev_ptr;
print_dev_t(*dev);
return 1;
}
static void
dm_decode_dm_target_deps(struct tcb *const tcp, const kernel_ulong_t addr,
const struct dm_ioctl *const ioc)
{
if (ioc->data_start == ioc->data_size)
return;
tprints(", ");
if (abbrev(tcp)) {
tprints("...");
return;
}
static const uint32_t target_deps_dev_offs =
offsetof(struct dm_target_deps, dev);
uint64_t dev_buf;
struct dm_target_deps s;
uint32_t offset = ioc->data_start;
uint32_t offset_end = offset + target_deps_dev_offs;
uint32_t space;
if (offset_end <= offset || offset_end > ioc->data_size)
goto misplaced;
if (umove_or_printaddr(tcp, addr + offset, &s))
return;
space = (ioc->data_size - offset_end) / sizeof(dev_buf);
if (s.count > space)
goto misplaced;
PRINT_FIELD_U("{", s, count);
tprints(", deps=");
print_array(tcp, addr + offset_end, s.count, &dev_buf, sizeof(dev_buf),
print_array: enhance printing of unfetchable object addresses When umoven_func invocation fails to fetch data, it prints the faulty address. If this happens to a subsequent umoven_func invocation, the printed address may be undistinguishable from a valid data printed by print_func, e.g. when the data is printed in a numeric form like [0x1, 0x2, 0x3, 0xdefaced]. Fix this source of confusion by moving the printing of the faulty address from umoven_func to print_array itself. This change renames umoven_func to tfetch_mem_func and changes its semantics, so that - tfetch_mem_func never prints anything; - tfetch_mem_func returns true if the fetch succeeded, and false otherwise. * defs.h (print_array): Replace umoven_func argument with tfetch_mem_func. * util.c (print_array): Replace umoven_func argument with tfetch_mem_func, document expected tfetch_mem_func return value semantics. When tfetch_mem_func returns false, print either addr or "... /* addr */" depending on the context (inside the array or not). * bpf.c (print_ebpf_prog, print_bpf_prog_info, BEGIN_BPF_CMD_DECODER(BPF_PROG_QUERY)): Replace umoven_or_printaddr argument of print_array with tfetch_mem. * bpf_filter.c (print_bpf_fprog): Likewise. * btrfs.c (btrfs_print_logical_ino_container, btrfs_print_ino_path_container, btrfs_print_qgroup_inherit, btrfs_ioctl): Likewise. * dm.c (dm_decode_dm_target_deps): Likewise. * epoll.c (epoll_wait_common): Likewise. * file_ioctl.c (file_ioctl): Likewise. * ipc_sem.c (tprint_sembuf_array): Likewise. * kexec.c (print_kexec_segments): Likewise. * mem.c (SYS_FUNC(subpage_prot)): Likewise. * net.c (print_getsockopt): Likewise. * netlink.c (decode_nlmsgerr_attr_cookie): Likewise. * netlink_netlink_diag.c (decode_netlink_diag_groups): Likewise. * netlink_packet_diag.c (decode_packet_diag_mclist): Likewise. * netlink_unix_diag.c (decode_unix_diag_inode): Likewise. * nlattr.c (decode_nla_meminfo): Likewise. * numa.c (print_nodemask, SYS_FUNC(move_pages), * perf_ioctl.c (perf_ioctl_query_bpf): Likewise. * poll.c (decode_poll_entering): Likewise. * printsiginfo.c (print_siginfo_array): Likewise. * rtnl_tc.c (decode_tca_stab_data): Likewise. * sock.c (decode_ifconf): Likewise. * uid.c (print_groups): Likewise. * io.c (SYS_FUNC(io_submit), SYS_FUNC(io_getevents)): Replace umoven_or_printaddr argument of print_array with tfetch_mem. (tprint_iov_upto): Replace umoven_or_printaddr_ignore_syserror with tfetch_mem_ignore_syserror. * v4l2.c (print_v4l2_format_fmt): Replace umoven_or_printaddr argument of print_array with tfetch_mem. (print_v4l2_ext_controls): Replace umoven_or_printaddr_ignore_syserror with tfetch_mem_ignore_syserror. * mmsghdr.c (fetch_struct_mmsghdr_or_printaddr): Rename to fetch_struct_mmsghdr_for_print, do not print address, return bool. (decode_mmsgvec): Replace fetch_struct_mmsghdr_or_printaddr with fetch_struct_mmsghdr_for_print. * tests/aio.c (main): Update expected output. * tests/bpf.c (print_BPF_PROG_QUERY_attr5): Likewise. * tests/ioctl_perf-success.c (main): Likewise. * tests/ioctl_v4l2.c (main): Update expected output. * tests/kexec_load.c (main): Likewise. * tests/mmsg_name.c (test_mmsg_name): Update expected output. * tests/move_pages.c (print_page_array, print_node_array): Likewise. * tests/poll.c (print_pollfd_array_entering): Likewise. * tests/preadv-pwritev.c (main): Likewise. * tests/preadv2-pwritev2.c (dumpio): Likewise. * tests/process_vm_readv_writev.c (print_iov): Likewise. * tests/pwritev.c (print_iovec): Likewise. * tests/readv.c (main): Likewise. * tests/seccomp-filter-v.c * tests/semop.c (main): Likewise. * tests/set_mempolicy.c (print_nodes): Likewise. * tests/setgroups.c (main): Likewise. * tests/test_nlattr.h (print_nlattr) Likewise. Co-Authored-by: Eugene Syromyatnikov <evgsyr@gmail.com>
2018-05-29 04:15:19 +03:00
tfetch_mem, dm_print_dev, NULL);
tprints("}");
return;
misplaced:
tprints("???");
tprints_comment("misplaced struct dm_target_deps");
}
static void
dm_decode_dm_name_list(struct tcb *const tcp, const kernel_ulong_t addr,
const struct dm_ioctl *const ioc)
{
static const uint32_t name_list_name_offs =
offsetof(struct dm_name_list, name);
struct dm_name_list s;
uint32_t offset = ioc->data_start;
uint32_t offset_end = 0;
uint32_t count;
int rc;
if (ioc->data_start == ioc->data_size)
return;
if (abbrev(tcp)) {
tprints(", ...");
return;
}
for (count = 0;; count++) {
tprints(", ");
if (count && offset <= offset_end)
goto misplaced;
offset_end = offset + name_list_name_offs;
if (offset_end <= offset || offset_end > ioc->data_size)
goto misplaced;
if (count >= max_strlen) {
tprints("...");
break;
}
if (umove_or_printaddr(tcp, addr + offset, &s))
break;
PRINT_FIELD_DEV("{", s, dev);
tprints(", name=");
rc = printstr_ex(tcp, addr + offset_end,
ioc->data_size - offset_end,
QUOTE_0_TERMINATED);
/*
* In Linux v4.13-rc1~137^2~13 it has been decided to cram in
* one more undocumented field after the device name, as if the
* format decoding was not twisted enough already. So, we have
* to check "next" now, and if it _looks like_ that there is
* a space for one additional integer, let's print it. As if the
* perversity with "name string going further than pointer to
* the next one" wasn't enough. Moreover, the calculation was
* broken for m32 on 64-bit kernels until v4.14-rc4~20^2~3, and
* we have no ability to detect kernel bit-ness (on x86, at
* least), so refrain from printing it for the DM versions below
* 4.37 (the original version was also aligned differently than
* now even on 64 bit).
*/
if ((rc > 0) && ioc->version[1] >= 37) {
kernel_ulong_t event_addr =
(addr + offset_end + rc + 7) & ~7;
uint32_t event_nr;
if ((event_addr + sizeof(event_nr)) <=
(addr + offset + s.next) &&
!umove(tcp, event_addr, &event_nr))
tprintf(", event_nr=%" PRIu32, event_nr);
}
tprints("}");
if (!s.next)
break;
offset += s.next;
}
return;
misplaced:
tprints("???");
tprints_comment("misplaced struct dm_name_list");
}
static void
dm_decode_dm_target_versions(struct tcb *const tcp, const kernel_ulong_t addr,
const struct dm_ioctl *const ioc)
{
static const uint32_t target_vers_name_offs =
offsetof(struct dm_target_versions, name);
struct dm_target_versions s;
uint32_t offset = ioc->data_start;
uint32_t offset_end = 0;
uint32_t count;
if (ioc->data_start == ioc->data_size)
return;
if (abbrev(tcp)) {
tprints(", ...");
return;
}
for (count = 0;; count++) {
tprints(", ");
if (count && offset <= offset_end)
goto misplaced;
offset_end = offset + target_vers_name_offs;
if (offset_end <= offset || offset_end > ioc->data_size)
goto misplaced;
if (count >= max_strlen) {
tprints("...");
break;
}
if (umove_or_printaddr(tcp, addr + offset, &s))
break;
tprints("{name=");
printstr_ex(tcp, addr + offset_end, ioc->data_size - offset_end,
QUOTE_0_TERMINATED);
tprintf(", version=%" PRIu32 ".%" PRIu32 ".%" PRIu32 "}",
s.version[0], s.version[1], s.version[2]);
if (!s.next)
break;
offset += s.next;
}
return;
misplaced:
tprints("???");
tprints_comment("misplaced struct dm_target_versions");
}
static void
dm_decode_dm_target_msg(struct tcb *const tcp, const kernel_ulong_t addr,
const struct dm_ioctl *const ioc)
{
if (ioc->data_start == ioc->data_size)
return;
tprints(", ");
if (abbrev(tcp)) {
tprints("...");
return;
}
static const uint32_t target_msg_message_offs =
offsetof(struct dm_target_msg, message);
uint32_t offset = ioc->data_start;
uint32_t offset_end = offset + target_msg_message_offs;
if (offset_end > offset && offset_end <= ioc->data_size) {
struct dm_target_msg s;
if (umove_or_printaddr(tcp, addr + offset, &s))
return;
PRINT_FIELD_U("{", s, sector);
tprints(", message=");
printstr_ex(tcp, addr + offset_end, ioc->data_size - offset_end,
QUOTE_0_TERMINATED);
tprints("}");
} else {
tprints("???");
tprints_comment("misplaced struct dm_target_msg");
}
}
static void
dm_decode_string(struct tcb *const tcp, const kernel_ulong_t addr,
const struct dm_ioctl *const ioc)
{
tprints(", ");
if (abbrev(tcp)) {
tprints("...");
return;
}
uint32_t offset = ioc->data_start;
if (offset <= ioc->data_size) {
tprints("string=");
printstr_ex(tcp, addr + offset, ioc->data_size - offset,
QUOTE_0_TERMINATED);
} else {
tprints("???");
tprints_comment("misplaced string");
}
}
static inline bool
dm_ioctl_has_params(const unsigned int code)
{
switch (code) {
case DM_VERSION:
case DM_REMOVE_ALL:
case DM_DEV_CREATE:
case DM_DEV_REMOVE:
case DM_DEV_SUSPEND:
case DM_DEV_STATUS:
case DM_TABLE_CLEAR:
case DM_DEV_ARM_POLL:
return false;
}
return true;
}
static int
dm_known_ioctl(struct tcb *const tcp, const unsigned int code,
const kernel_ulong_t arg)
{
struct dm_ioctl *ioc = NULL;
struct dm_ioctl *entering_ioc = NULL;
bool ioc_changed = false;
if (entering(tcp)) {
ioc = malloc(sizeof(*ioc));
if (!ioc)
return 0;
} else {
ioc = alloca(sizeof(*ioc));
}
if ((umoven(tcp, arg, offsetof(struct dm_ioctl, data), ioc) < 0) ||
(ioc->data_size < offsetof(struct dm_ioctl, data_size))) {
if (entering(tcp))
free(ioc);
return 0;
}
if (entering(tcp))
set_tcb_priv_data(tcp, ioc, free);
else {
entering_ioc = get_tcb_priv_data(tcp);
/*
* retrieve_status, __dev_status called only in case of success,
* so it looks like there's no need to check open_count,
* event_nr, target_count, dev fields for change (they are
* printed only in case of absence of errors).
*/
if (!entering_ioc ||
(ioc->version[0] != entering_ioc->version[0]) ||
(ioc->version[1] != entering_ioc->version[1]) ||
(ioc->version[2] != entering_ioc->version[2]) ||
(ioc->data_size != entering_ioc->data_size) ||
(ioc->data_start != entering_ioc->data_start) ||
(ioc->flags != entering_ioc->flags))
ioc_changed = true;
}
if (exiting(tcp) && syserror(tcp) && !ioc_changed)
return RVAL_IOCTL_DECODED;
/*
* device mapper code uses %d in some places and %u in another, but
* fields themselves are declared as __u32.
*/
tprintf("%s{version=%u.%u.%u", entering(tcp) ? ", " : " => ",
ioc->version[0], ioc->version[1], ioc->version[2]);
/*
* if we use a different version of ABI, do not attempt to decode
* ioctl fields
*/
if (ioc->version[0] != DM_VERSION_MAJOR) {
tprints_comment("unsupported device mapper ABI version");
goto skip;
}
PRINT_FIELD_U(", ", *ioc, data_size);
if (ioc->data_size < offsetof(struct dm_ioctl, data)) {
tprints_comment("data_size too small");
goto skip;
}
if (dm_ioctl_has_params(code))
PRINT_FIELD_U(", ", *ioc, data_start);
dm_decode_device(code, ioc);
dm_decode_values(tcp, code, ioc);
dm_decode_flags(ioc);
switch (code) {
case DM_DEV_WAIT:
case DM_TABLE_STATUS:
if (entering(tcp) || syserror(tcp))
break;
dm_decode_dm_target_spec(tcp, arg, ioc);
break;
case DM_TABLE_LOAD:
if (exiting(tcp))
break;
dm_decode_dm_target_spec(tcp, arg, ioc);
break;
case DM_TABLE_DEPS:
if (entering(tcp) || syserror(tcp))
break;
dm_decode_dm_target_deps(tcp, arg, ioc);
break;
case DM_LIST_DEVICES:
if (entering(tcp) || syserror(tcp))
break;
dm_decode_dm_name_list(tcp, arg, ioc);
break;
case DM_LIST_VERSIONS:
if (entering(tcp) || syserror(tcp))
break;
dm_decode_dm_target_versions(tcp, arg, ioc);
break;
case DM_TARGET_MSG:
if (entering(tcp))
dm_decode_dm_target_msg(tcp, arg, ioc);
else if (!syserror(tcp) && ioc->flags & DM_DATA_OUT_FLAG)
dm_decode_string(tcp, arg, ioc);
break;
case DM_DEV_RENAME:
case DM_DEV_SET_GEOMETRY:
if (exiting(tcp))
break;
dm_decode_string(tcp, arg, ioc);
break;
}
skip:
tprints("}");
return entering(tcp) ? 0 : RVAL_IOCTL_DECODED;
}
int
dm_ioctl(struct tcb *const tcp, const unsigned int code, const kernel_ulong_t arg)
{
switch (code) {
case DM_VERSION:
case DM_REMOVE_ALL:
case DM_LIST_DEVICES:
case DM_DEV_CREATE:
case DM_DEV_REMOVE:
case DM_DEV_RENAME:
case DM_DEV_SUSPEND:
case DM_DEV_STATUS:
case DM_DEV_WAIT:
case DM_TABLE_LOAD:
case DM_TABLE_CLEAR:
case DM_TABLE_DEPS:
case DM_TABLE_STATUS:
case DM_LIST_VERSIONS:
case DM_TARGET_MSG:
case DM_DEV_SET_GEOMETRY:
case DM_DEV_ARM_POLL:
return dm_known_ioctl(tcp, code, arg);
default:
return RVAL_DECODED;
}
}
# else /* !(DM_VERSION_MAJOR == 4) */
int
dm_ioctl(struct tcb *const tcp, const unsigned int code, const kernel_ulong_t arg)
{
return RVAL_DECODED;
}
# endif /* DM_VERSION_MAJOR == 4 */
#endif /* HAVE_LINUX_DM_IOCTL_H */