diff options
author | Dmitry V. Levin <ldv@altlinux.org> | 2020-04-12 00:32:29 +0000 |
---|---|---|
committer | Dmitry V. Levin <ldv@altlinux.org> | 2020-04-12 00:32:29 +0000 |
commit | 048fd0a024a01770d6b0dde2a1292cf97867a5e1 (patch) | |
tree | f8f52fac2ab7d30a70bf4b87169518b91340ee5b | |
parent | 9265a9daab24ad950718a5cccd55ff99269d0070 (diff) | |
download | strace-048fd0a024a01770d6b0dde2a1292cf97867a5e1.tar.gz |
Rewrite decoders of getdents, getdents64, and readdir syscalls
The old approach of allocating memory for all dentries returned by
getdents/getdents64 was problematic, fix it by fetching and printing
dentries sequentially.
* dirent_types.c: New file.
* xgetdents.c: Likewise.
* xgetdents.h: Likewise.
* Makefile.am (strace_SOURCES): Add them.
* dirent.c: Include "xgetdents.h" and "print_fields.h".
(header_size): New variable.
(print_dentry_head, decode_dentry_head, decode_dentry_tail): New
functions.
(print_old_dirent): Rewrite using print_dentry_head.
(SYS_FUNC(getdents)): Rewrite using xgetdents, decode_dentry_head,
and decode_dentry_tail.
* dirent64.c: Include "xgetdents.h", "kernel_dirent.h"
and "print_fields.h" instead of "defs.h", <dirent.h>
and "xlat/dirent_types.h".
(decode_dentry_head, decode_dentry_tail): New functions.
(SYS_FUNC(getdents64)): Rewrite using xgetdents, decode_dentry_head,
and decode_dentry_tail.
Resolves: https://github.com/strace/strace/issues/19
Resolves: https://github.com/strace/strace/pull/20
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | dirent.c | 169 | ||||
-rw-r--r-- | dirent64.c | 130 | ||||
-rw-r--r-- | dirent_types.c | 11 | ||||
-rw-r--r-- | xgetdents.c | 141 | ||||
-rw-r--r-- | xgetdents.h | 21 |
6 files changed, 287 insertions, 188 deletions
diff --git a/Makefile.am b/Makefile.am index aeb3530e2..c56897a4c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -101,6 +101,7 @@ strace_SOURCES = \ desc.c \ dirent.c \ dirent64.c \ + dirent_types.c \ dm.c \ dyxlat.c \ empty.h \ @@ -365,6 +366,8 @@ strace_SOURCES = \ watchdog_ioctl.c \ xattr.c \ xfs_quota_stat.h \ + xgetdents.c \ + xgetdents.h \ xlat.c \ xlat.h \ xmalloc.c \ @@ -17,22 +17,82 @@ #include MPERS_DEFS +#include "xgetdents.h" +#include "print_fields.h" + #define D_NAME_LEN_MAX 256 +/* The minimum size of a valid directory entry. */ +static const unsigned int header_size = + offsetof(kernel_dirent_t, d_name); + +static void +print_dentry_head(const kernel_dirent_t *const dent) +{ + PRINT_FIELD_U("{", *dent, d_ino); + PRINT_FIELD_U(", ", *dent, d_off); + PRINT_FIELD_U(", ", *dent, d_reclen); +} + +static unsigned int +decode_dentry_head(struct tcb *const tcp, const void *const arg) +{ + const kernel_dirent_t *const dent = arg; + + if (!abbrev(tcp)) + print_dentry_head(dent); + + return dent->d_reclen; +} + +static int +decode_dentry_tail(struct tcb *const tcp, kernel_ulong_t addr, + const void *const arg, const unsigned int d_name_type_len) +{ + int rc = 0; + + /* !abbrev(tcp) */ + + if (d_name_type_len) { + unsigned int d_name_len = d_name_type_len - 1; + if (d_name_len) { + if (d_name_len > D_NAME_LEN_MAX) + d_name_len = D_NAME_LEN_MAX; + tprints(", d_name="); + rc = printpathn(tcp, addr, d_name_len - 1); + } + tprints(", d_type="); + const kernel_ulong_t d_type_addr = + addr + (d_name_type_len - 1); + unsigned char d_type; + if (umove_or_printaddr(tcp, d_type_addr, &d_type)) + rc = -1; + else + printxval(dirent_types, d_type, "DT_???"); + } + tprints("}"); + + return rc; +} + +SYS_FUNC(getdents) +{ + return xgetdents(tcp, header_size, + decode_dentry_head, decode_dentry_tail); +} + static void print_old_dirent(struct tcb *const tcp, const kernel_ulong_t addr) { - kernel_dirent_t d; + kernel_dirent_t dent; - if (umove_or_printaddr(tcp, addr, &d)) + if (umove_or_printaddr(tcp, addr, &dent)) return; - tprintf("{d_ino=%llu, d_off=%llu, d_reclen=%u, d_name=", - zero_extend_signed_to_ull(d.d_ino), - zero_extend_signed_to_ull(d.d_off), d.d_reclen); - if (d.d_reclen > D_NAME_LEN_MAX) - d.d_reclen = D_NAME_LEN_MAX; - printpathn(tcp, addr + offsetof(kernel_dirent_t, d_name), d.d_reclen); + print_dentry_head(&dent); + tprints(", d_name="); + printpathn(tcp, addr + header_size, + MIN(dent.d_reclen, D_NAME_LEN_MAX)); tprints("}"); } @@ -46,99 +106,10 @@ SYS_FUNC(readdir) printaddr(tcp->u_arg[1]); else print_old_dirent(tcp, tcp->u_arg[1]); + const unsigned int count = tcp->u_arg[2]; /* Not much point in printing this out, it is always 1. */ - if (tcp->u_arg[2] != 1) - tprintf(", %" PRI_klu, tcp->u_arg[2]); - } - return 0; -} - -SYS_FUNC(getdents) -{ - unsigned int i, len, dents = 0; - unsigned char *buf; - - if (entering(tcp)) { - printfd(tcp, tcp->u_arg[0]); - return 0; - } - - tprints(", "); - - const unsigned int count = tcp->u_arg[2]; - - if (syserror(tcp) || !verbose(tcp) || - (kernel_ulong_t) tcp->u_rval > count /* kernel gone bananas? */) { - printaddr(tcp->u_arg[1]); - tprintf(", %u", count); - return 0; - } - - /* Beware of insanely large or too small values in tcp->u_rval */ - if (tcp->u_rval > 1024*1024) - len = 1024*1024; - else if (tcp->u_rval < (int) sizeof(kernel_dirent_t)) - len = 0; - else - len = tcp->u_rval; - - if (len) { - buf = malloc(len); - if (!buf || umoven(tcp, tcp->u_arg[1], len, buf) < 0) { - printaddr(tcp->u_arg[1]); + if (count != 1) tprintf(", %u", count); - free(buf); - return 0; - } - } else { - buf = NULL; - } - - if (abbrev(tcp)) - printaddr(tcp->u_arg[1]); - else - tprints("["); - - for (i = 0; len && i <= len - sizeof(kernel_dirent_t); ) { - kernel_dirent_t *d = (kernel_dirent_t *) &buf[i]; - - if (!abbrev(tcp)) { - int oob = d->d_reclen < sizeof(kernel_dirent_t) || - i + d->d_reclen - 1 >= len; - int d_name_len = oob ? len - i : d->d_reclen; - d_name_len -= offsetof(kernel_dirent_t, d_name) + 1; - if (d_name_len > D_NAME_LEN_MAX) - d_name_len = D_NAME_LEN_MAX; - - tprintf("%s{d_ino=%llu, d_off=%llu, d_reclen=%u" - ", d_name=", i ? ", " : "", - zero_extend_signed_to_ull(d->d_ino), - zero_extend_signed_to_ull(d->d_off), - d->d_reclen); - - print_quoted_cstring(d->d_name, d_name_len); - - tprints(", d_type="); - if (oob) - tprints("?"); - else - printxval(dirent_types, buf[i + d->d_reclen - 1], "DT_???"); - tprints("}"); - } - dents++; - if (d->d_reclen < sizeof(kernel_dirent_t)) { - tprints_comment("d_reclen < sizeof(struct dirent)"); - break; - } - i += d->d_reclen; } - - if (abbrev(tcp)) - tprintf_comment("%u entries", dents); - else - tprints("]"); - - tprintf(", %u", count); - free(buf); return 0; } diff --git a/dirent64.c b/dirent64.c index cefcfd849..40ab4b3dc 100644 --- a/dirent64.c +++ b/dirent64.c @@ -1,111 +1,63 @@ /* - * Copyright (c) 1991, 1992 Paul Kranenburg <pk@cs.few.eur.nl> - * Copyright (c) 1993 Branko Lankester <branko@hacktic.nl> - * Copyright (c) 1993, 1994, 1995, 1996 Rick Sladkey <jrs@world.std.com> - * Copyright (c) 1996-1999 Wichert Akkerman <wichert@cistron.nl> - * Copyright (c) 2005-2015 Dmitry V. Levin <ldv@altlinux.org> - * Copyright (c) 2015-2018 The strace developers. + * Copyright (c) 2020 Dmitry V. Levin <ldv@altlinux.org> * All rights reserved. * * SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "defs.h" -#include <dirent.h> - -#include "xlat/dirent_types.h" +#include "xgetdents.h" +#include "kernel_dirent.h" +#include "print_fields.h" #define D_NAME_LEN_MAX 256 -SYS_FUNC(getdents64) +static void +print_dentry_head(const kernel_dirent64_t *const dent) { - /* the minimum size of a valid dirent64 structure */ - const unsigned int d_name_offset = offsetof(struct dirent64, d_name); + PRINT_FIELD_U("{", *dent, d_ino); + PRINT_FIELD_U(", ", *dent, d_off); + PRINT_FIELD_U(", ", *dent, d_reclen); +} - unsigned int i, len, dents = 0; - char *buf; +static unsigned int +decode_dentry_head(struct tcb *const tcp, const void *const arg) +{ + const kernel_dirent64_t *const dent = arg; - if (entering(tcp)) { - printfd(tcp, tcp->u_arg[0]); - return 0; - } + if (!abbrev(tcp)) + print_dentry_head(dent); - tprints(", "); + return dent->d_reclen; +} - const unsigned int count = tcp->u_arg[2]; +static int +decode_dentry_tail(struct tcb *const tcp, kernel_ulong_t addr, + const void *const arg, unsigned int d_name_len) +{ + const kernel_dirent64_t *const dent = arg; + int rc = 0; - if (syserror(tcp) || !verbose(tcp) || - (kernel_ulong_t) tcp->u_rval > count /* kernel gone bananas? */) { - printaddr(tcp->u_arg[1]); - tprintf(", %u", count); - return 0; - } + /* !abbrev(tcp) */ - /* Beware of insanely large or too small values in tcp->u_rval */ - if (tcp->u_rval > 1024*1024) - len = 1024*1024; - else if (tcp->u_rval < (int) d_name_offset) - len = 0; - else - len = tcp->u_rval; + PRINT_FIELD_XVAL(", ", *dent, d_type, dirent_types, "DT_???"); - if (len) { - buf = malloc(len); - if (!buf || umoven(tcp, tcp->u_arg[1], len, buf) < 0) { - printaddr(tcp->u_arg[1]); - tprintf(", %u", count); - free(buf); - return 0; - } - } else { - buf = NULL; + if (d_name_len) { + if (d_name_len > D_NAME_LEN_MAX) + d_name_len = D_NAME_LEN_MAX; + tprints(", d_name="); + rc = printpathn(tcp, addr, d_name_len - 1); } + tprints("}"); - if (abbrev(tcp)) - printaddr(tcp->u_arg[1]); - else - tprints("["); - - for (i = 0; len && i <= len - d_name_offset; ) { - struct dirent64 *d = (struct dirent64 *) &buf[i]; - if (!abbrev(tcp)) { - int d_name_len; - if (d->d_reclen >= d_name_offset - && i + d->d_reclen <= len) { - d_name_len = d->d_reclen - d_name_offset; - } else { - d_name_len = len - i - d_name_offset; - } - if (d_name_len > D_NAME_LEN_MAX) - d_name_len = D_NAME_LEN_MAX; - - tprintf("%s{d_ino=%" PRIu64 ", d_off=%" PRId64 - ", d_reclen=%u, d_type=", - i ? ", " : "", - d->d_ino, - d->d_off, - d->d_reclen); - printxval(dirent_types, d->d_type, "DT_???"); - - tprints(", d_name="); - print_quoted_cstring(d->d_name, d_name_len); - - tprints("}"); - } - if (d->d_reclen < d_name_offset) { - tprints_comment("d_reclen < offsetof(struct dirent64, d_name)"); - break; - } - i += d->d_reclen; - dents++; - } + return rc; +} - if (abbrev(tcp)) - tprintf_comment("%u entries", dents); - else - tprints("]"); +SYS_FUNC(getdents64) +{ + /* The minimum size of a valid directory entry. */ + static const unsigned int header_size = + offsetof(kernel_dirent64_t, d_name); - tprintf(", %u", count); - free(buf); - return 0; + return xgetdents(tcp, header_size, + decode_dentry_head, decode_dentry_tail); } diff --git a/dirent_types.c b/dirent_types.c new file mode 100644 index 000000000..aafe3337a --- /dev/null +++ b/dirent_types.c @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2005-2015 Dmitry V. Levin <ldv@altlinux.org> + * Copyright (c) 2015-2020 The strace developers. + * All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "defs.h" +#include <dirent.h> +#include "xlat/dirent_types.h" diff --git a/xgetdents.c b/xgetdents.c new file mode 100644 index 000000000..80bf49ffc --- /dev/null +++ b/xgetdents.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2020 Dmitry V. Levin <ldv@altlinux.org> + * All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "xgetdents.h" +#include "kernel_dirent.h" + +static void +decode_dents(struct tcb *const tcp, kernel_ulong_t addr, unsigned int len, + const unsigned int header_size, + const decode_dentry_head_fn decode_dentry_head, + const decode_dentry_tail_fn decode_dentry_tail) +{ + union { + kernel_dirent_t ent; + kernel_dirent64_t ent64; + } dent; + unsigned int count = 0; + + if (abbrev(tcp)) + printaddr(addr); + + for (;;) { + if (len < header_size) { + if (!abbrev(tcp)) { + if (!len) { + tprints("["); + ++count; + } else { + printstr_ex(tcp, addr, len, + QUOTE_FORCE_HEX); + } + } + break; + } + + /* len >= header_size after this point. */ + if (!tfetch_mem(tcp, addr, header_size, &dent)) { + if (!abbrev(tcp)) { + if (count) { + tprints("..."); + printaddr_comment(addr); + } else { + printaddr(addr); + } + } + + break; + } + + if (!abbrev(tcp)) { + if (!count) + tprints("["); + } + ++count; + + kernel_ulong_t next_addr = 0; + unsigned int next_len = 0; + unsigned int d_reclen = decode_dentry_head(tcp, &dent); + + if (d_reclen > len) { + /* cannot happen? */ + tprintf_comment("%s%u bytes overflow", + (abbrev(tcp) ? "d_reclen " : ""), + d_reclen - len); + d_reclen = len; + } else if (d_reclen < header_size) { + /* cannot happen? */ + tprintf_comment("%s%u bytes underflow", + (abbrev(tcp) ? "d_reclen " : ""), + header_size - d_reclen); + d_reclen = header_size; + next_len = len - header_size; + } else { + next_len = len - d_reclen; + if (next_len) { + if (addr + d_reclen > addr) { + next_addr = addr + d_reclen; + } else { + /* cannot happen? */ + tprints_comment("address overflow"); + } + } + } + + len = next_len; + /* Do not use len inside the loop after this point. */ + + if (!abbrev(tcp)) { + int rc = decode_dentry_tail(tcp, addr + header_size, + &dent, + d_reclen - header_size); + if (next_addr) { + tprints(", "); + if (rc < 0) { + tprints("..."); + break; + } + } + } + + if (!next_addr) + break; + addr = next_addr; + } + + if (!abbrev(tcp)) { + if (count) + tprints("]"); + } else { + tprintf_comment("%u%s entries", count, len ? "+" : ""); + } +} + +int +xgetdents(struct tcb *const tcp, const unsigned int header_size, + const decode_dentry_head_fn decode_dentry_head, + const decode_dentry_tail_fn decode_dentry_tail) +{ + if (entering(tcp)) { + printfd(tcp, tcp->u_arg[0]); + tprints(", "); + return 0; + } + + const unsigned int count = tcp->u_arg[2]; + + if (syserror(tcp) || !verbose(tcp) || + (kernel_ulong_t) tcp->u_rval > count /* kernel gone bananas? */) { + printaddr(tcp->u_arg[1]); + } else { + decode_dents(tcp, tcp->u_arg[1], tcp->u_rval, header_size, + decode_dentry_head, decode_dentry_tail); + } + + tprintf(", %u", count); + return 0; +} diff --git a/xgetdents.h b/xgetdents.h new file mode 100644 index 000000000..c9060c009 --- /dev/null +++ b/xgetdents.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 Dmitry V. Levin <ldv@altlinux.org> + * All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef STRACE_XGETDENTS_H +# define STRACE_XGETDENTS_H + +#include "defs.h" + +typedef unsigned int (*decode_dentry_head_fn)(struct tcb *, const void *); +typedef int (*decode_dentry_tail_fn)(struct tcb *, kernel_ulong_t, + const void *, unsigned int); + +extern int +xgetdents(struct tcb *, unsigned int header_size, + decode_dentry_head_fn, decode_dentry_tail_fn); + +#endif /* !STRACE_XGETDENTS_H */ |