summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry V. Levin <ldv@altlinux.org>2020-04-12 00:32:29 +0000
committerDmitry V. Levin <ldv@altlinux.org>2020-04-12 00:32:29 +0000
commit048fd0a024a01770d6b0dde2a1292cf97867a5e1 (patch)
treef8f52fac2ab7d30a70bf4b87169518b91340ee5b
parent9265a9daab24ad950718a5cccd55ff99269d0070 (diff)
downloadstrace-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.am3
-rw-r--r--dirent.c169
-rw-r--r--dirent64.c130
-rw-r--r--dirent_types.c11
-rw-r--r--xgetdents.c141
-rw-r--r--xgetdents.h21
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 \
diff --git a/dirent.c b/dirent.c
index 98ee85292..6e4d09a54 100644
--- a/dirent.c
+++ b/dirent.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 */