/* * Support for decoding of DM_* ioctl commands. * * Copyright (c) 2016 Mikulas Patocka * Copyright (c) 2016 Masatake Yamato * Copyright (c) 2016 Dmitry V. Levin * Copyright (c) 2016 Eugene Syromyatnikov * Copyright (c) 2016-2018 The strace developers. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "defs.h" #ifdef HAVE_LINUX_DM_IOCTL_H # include "print_fields.h" # include # include # 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), 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 */