Fix crashing in calls through untraced slots in prelinked PPC64 binaries

This commit is contained in:
Petr Machata 2014-01-10 20:31:38 +01:00
parent 2128805bc3
commit d4e3c89949
3 changed files with 317 additions and 1 deletions

View File

@ -0,0 +1,81 @@
From 56134ff5442bee4e128b189bb86cfc97dcb6f60a Mon Sep 17 00:00:00 2001
From: Petr Machata <pmachata@redhat.com>
Date: Fri, 10 Jan 2014 20:05:15 +0100
Subject: [PATCH 1/2] Add a new per-breakpoint callback on_install
---
breakpoint.h | 9 ++++++++-
breakpoints.c | 11 ++++++++++-
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/breakpoint.h b/breakpoint.h
index 95964a8..c36f673 100644
--- a/breakpoint.h
+++ b/breakpoint.h
@@ -1,6 +1,6 @@
/*
* This file is part of ltrace.
- * Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2012,2013,2014 Petr Machata, Red Hat Inc.
* Copyright (C) 2009 Juan Cespedes
*
* This program is free software; you can redistribute it and/or
@@ -46,6 +46,7 @@
struct bp_callbacks {
void (*on_hit)(struct breakpoint *bp, struct process *proc);
void (*on_continue)(struct breakpoint *bp, struct process *proc);
+ void (*on_install)(struct breakpoint *bp, struct process *proc);
void (*on_retract)(struct breakpoint *bp, struct process *proc);
/* Create a new breakpoint that should handle return from the
@@ -84,6 +85,12 @@ void breakpoint_on_continue(struct breakpoint *bp, struct process *proc);
* the instruction underneath it). */
void breakpoint_on_retract(struct breakpoint *bp, struct process *proc);
+/* Call ON_INSTALL handler of BP, if any is set. This should be
+ * called after the breakpoint is enabled for the first time, not
+ * every time it's enabled (such as after stepping over a site of a
+ * temporarily disabled breakpoint). */
+void breakpoint_on_install(struct breakpoint *bp, struct process *proc);
+
/* Call GET_RETURN_BP handler of BP, if any is set. If none is set,
* call CREATE_DEFAULT_RETURN_BP to obtain one. */
int breakpoint_get_return_bp(struct breakpoint **ret,
diff --git a/breakpoints.c b/breakpoints.c
index 947cb71..c3fa275 100644
--- a/breakpoints.c
+++ b/breakpoints.c
@@ -1,6 +1,6 @@
/*
* This file is part of ltrace.
- * Copyright (C) 2006,2007,2011,2012,2013 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2006,2007,2011,2012,2013,2014 Petr Machata, Red Hat Inc.
* Copyright (C) 2009 Juan Cespedes
* Copyright (C) 1998,2001,2002,2003,2007,2008,2009 Juan Cespedes
* Copyright (C) 2006 Ian Wienand
@@ -85,6 +85,14 @@ breakpoint_on_retract(struct breakpoint *bp, struct process *proc)
(bp->cbs->on_retract)(bp, proc);
}
+void
+breakpoint_on_install(struct breakpoint *bp, struct process *proc)
+{
+ assert(bp != NULL);
+ if (bp->cbs != NULL && bp->cbs->on_install != NULL)
+ (bp->cbs->on_install)(bp, proc);
+}
+
int
breakpoint_get_return_bp(struct breakpoint **ret,
struct breakpoint *bp, struct process *proc)
@@ -229,6 +237,7 @@ breakpoint_turn_on(struct breakpoint *bp, struct process *proc)
if (bp->enabled == 1) {
assert(proc->pid != 0);
enable_breakpoint(proc, bp);
+ breakpoint_on_install(bp, proc);
}
return 0;
}
--
1.7.6.5

View File

@ -0,0 +1,221 @@
From a0093ca43cf40d7e5f6cebeb64156062d2de46d9 Mon Sep 17 00:00:00 2001
From: Petr Machata <pmachata@redhat.com>
Date: Fri, 10 Jan 2014 20:06:51 +0100
Subject: [PATCH 2/2] Don't crash untraced calls via PLT in prelinked PPC64
binaries
In prelinked binaries, ltrace has to unprelinks PLT slots in order to
catch calls done through PLT. This makes the calls done through these
slots invalid, because the special first PLT slot is not initialized,
and dynamic linker SIGSEGVs because of this. Ltrace relies on
arranging breakpoints such that the dynamic linker is not actually
entered, and moves PC around itself to simulate the effects of a call
through PLT.
Originally, arch_elf_add_plt_entry was called only for symbols that
were actually traced. Later this was changed and it's now called for
all PLT entries, and the resulting candidate list is filtered
afterwards. This gives backends a chance to rename the symbol, as is
useful with IRELATIVE PLT calls, where symbol name may not be
available at all. But the PPC backend was never updated to reflect
this, and unresolved all symbols for which arch_elf_add_plt_entry was
called, thus rendering _all_ PLT slots invalid, even those that
weren't later procted by breakpoints. Thus calls done through any
untraced slots failed.
This patch fixes this problem by deferring the unprelinking of PLT
slots into the on_install hook of breakpoints.
---
sysdeps/linux-gnu/ppc/arch.h | 21 ++++++++-
sysdeps/linux-gnu/ppc/plt.c | 94 +++++++++++++++++++++++++++++++++--------
2 files changed, 94 insertions(+), 21 deletions(-)
diff --git a/sysdeps/linux-gnu/ppc/arch.h b/sysdeps/linux-gnu/ppc/arch.h
index 2add3b8..bf9b5dc 100644
--- a/sysdeps/linux-gnu/ppc/arch.h
+++ b/sysdeps/linux-gnu/ppc/arch.h
@@ -1,6 +1,6 @@
/*
* This file is part of ltrace.
- * Copyright (C) 2012,2013 Petr Machata
+ * Copyright (C) 2012,2013,2014 Petr Machata
* Copyright (C) 2006 Paul Gilliam
* Copyright (C) 2002,2004 Juan Cespedes
*
@@ -87,12 +87,29 @@ enum ppc64_plt_type {
/* Very similar to PPC_PLT_UNRESOLVED, but for JMP_IREL
* slots. */
PPC_PLT_IRELATIVE,
+
+ /* Transitional state before the breakpoint is enabled. */
+ PPC_PLT_NEED_UNRESOLVE,
};
#define ARCH_HAVE_LIBRARY_SYMBOL_DATA
+struct ppc_unresolve_data;
struct arch_library_symbol_data {
enum ppc64_plt_type type;
- GElf_Addr resolved_value;
+
+ /* State Contents
+ *
+ * PPC_DEFAULT N/A
+ * PPC64_PLT_STUB N/A
+ * PPC_PLT_UNRESOLVED PLT entry address.
+ * PPC_PLT_IRELATIVE Likewise.
+ * PPC_PLT_RESOLVED The original value the slot was resolved to.
+ * PPC_PLT_NEED_UNRESOLVE DATA.
+ */
+ union {
+ GElf_Addr resolved_value;
+ struct ppc_unresolve_data *data;
+ };
/* Address of corresponding slot in .plt. */
GElf_Addr plt_slot_addr;
diff --git a/sysdeps/linux-gnu/ppc/plt.c b/sysdeps/linux-gnu/ppc/plt.c
index 8715da6..332daa8 100644
--- a/sysdeps/linux-gnu/ppc/plt.c
+++ b/sysdeps/linux-gnu/ppc/plt.c
@@ -679,6 +679,14 @@ arch_elf_add_func_entry(struct process *proc, struct ltelf *lte,
return PLT_OK;
}
+struct ppc_unresolve_data {
+ struct ppc_unresolve_data *self; /* A canary. */
+ GElf_Addr plt_entry_addr;
+ GElf_Addr plt_slot_addr;
+ GElf_Addr plt_slot_value;
+ bool is_irelative;
+};
+
enum plt_status
arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte,
const char *a_name, GElf_Rela *rela, size_t ndx,
@@ -778,28 +786,23 @@ arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte,
&& (plt_slot_value == plt_entry_addr || plt_slot_value == 0)) {
libsym->arch.type = PPC_PLT_UNRESOLVED;
libsym->arch.resolved_value = plt_entry_addr;
-
} else {
- /* Unresolve the .plt slot. If the binary was
- * prelinked, this makes the code invalid, because in
- * case of prelinked binary, the dynamic linker
- * doesn't update .plt[0] and .plt[1] with addresses
- * of the resover. But we don't care, we will never
- * need to enter the resolver. That just means that
- * we have to un-un-resolve this back before we
- * detach. */
-
- if (unresolve_plt_slot(proc, plt_slot_addr, plt_entry_addr) < 0) {
- library_symbol_destroy(libsym);
+ /* Mark the symbol for later unresolving. We may not
+ * do this right away, as this is called by ltrace
+ * core for all symbols, and only later filtered. We
+ * only unresolve the symbol before the breakpoint is
+ * enabled. */
+
+ libsym->arch.type = PPC_PLT_NEED_UNRESOLVE;
+ libsym->arch.data = malloc(sizeof *libsym->arch.data);
+ if (libsym->arch.data == NULL)
goto fail2;
- }
- if (! is_irelative) {
- mark_as_resolved(libsym, plt_slot_value);
- } else {
- libsym->arch.type = PPC_PLT_IRELATIVE;
- libsym->arch.resolved_value = plt_entry_addr;
- }
+ libsym->arch.data->self = libsym->arch.data;
+ libsym->arch.data->plt_entry_addr = plt_entry_addr;
+ libsym->arch.data->plt_slot_addr = plt_slot_addr;
+ libsym->arch.data->plt_slot_value = plt_slot_value;
+ libsym->arch.data->is_irelative = is_irelative;
}
*ret = libsym;
@@ -999,6 +1002,7 @@ ppc_plt_bp_continue(struct breakpoint *bp, struct process *proc)
return;
case PPC64_PLT_STUB:
+ case PPC_PLT_NEED_UNRESOLVE:
/* These should never hit here. */
break;
}
@@ -1050,6 +1054,52 @@ ppc_plt_bp_retract(struct breakpoint *bp, struct process *proc)
}
}
+static void
+ppc_plt_bp_install(struct breakpoint *bp, struct process *proc)
+{
+ /* This should not be an artificial breakpoint. */
+ struct library_symbol *libsym = bp->libsym;
+ if (libsym == NULL)
+ libsym = bp->arch.irel_libsym;
+ assert(libsym != NULL);
+
+ if (libsym->arch.type == PPC_PLT_NEED_UNRESOLVE) {
+ /* Unresolve the .plt slot. If the binary was
+ * prelinked, this makes the code invalid, because in
+ * case of prelinked binary, the dynamic linker
+ * doesn't update .plt[0] and .plt[1] with addresses
+ * of the resover. But we don't care, we will never
+ * need to enter the resolver. That just means that
+ * we have to un-un-resolve this back before we
+ * detach. */
+
+ struct ppc_unresolve_data *data = libsym->arch.data;
+ libsym->arch.data = NULL;
+ assert(data->self == data);
+
+ GElf_Addr plt_slot_addr = data->plt_slot_addr;
+ GElf_Addr plt_slot_value = data->plt_slot_value;
+ GElf_Addr plt_entry_addr = data->plt_entry_addr;
+
+ if (unresolve_plt_slot(proc, plt_slot_addr,
+ plt_entry_addr) == 0) {
+ if (! data->is_irelative) {
+ mark_as_resolved(libsym, plt_slot_value);
+ } else {
+ libsym->arch.type = PPC_PLT_IRELATIVE;
+ libsym->arch.resolved_value = plt_entry_addr;
+ }
+ } else {
+ fprintf(stderr, "Couldn't unresolve %s@%p. Not tracing"
+ " this symbol.\n",
+ breakpoint_name(bp), bp->addr);
+ proc_remove_breakpoint(proc, bp);
+ }
+
+ free(data);
+ }
+}
+
int
arch_library_init(struct library *lib)
{
@@ -1080,6 +1130,11 @@ arch_library_symbol_init(struct library_symbol *libsym)
void
arch_library_symbol_destroy(struct library_symbol *libsym)
{
+ if (libsym->arch.type == PPC_PLT_NEED_UNRESOLVE) {
+ assert(libsym->arch.data->self == libsym->arch.data);
+ free(libsym->arch.data);
+ libsym->arch.data = NULL;
+ }
}
int
@@ -1115,6 +1170,7 @@ arch_breakpoint_init(struct process *proc, struct breakpoint *bp)
static struct bp_callbacks cbs = {
.on_continue = ppc_plt_bp_continue,
.on_retract = ppc_plt_bp_retract,
+ .on_install = ppc_plt_bp_install,
};
breakpoint_set_callbacks(bp, &cbs);
--
1.7.6.5

View File

@ -1,7 +1,7 @@
Summary: Tracks runtime library calls from dynamically linked executables
Name: ltrace
Version: 0.7.91
Release: 3%{?dist}
Release: 4%{?dist}
URL: http://ltrace.alioth.debian.org/
License: GPLv2+
Group: Development/Debuggers
@ -24,6 +24,12 @@ Patch1: ltrace-0.7.91-account_execl.patch
# Fix for a regression in tracing across fork. Upstream patch.
Patch2: ltrace-0.7.91-ppc64-fork.patch
# Fix crashing a prelinked PPC64 binary which makes PLT calls through
# slots that ltrace doesn't trace.
# https://bugzilla.redhat.com/show_bug.cgi?id=1051221
Patch3: ltrace-0.7.91-breakpoint-on_install.patch
Patch4: ltrace-0.7.91-ppc64-unprelink.patch
%description
Ltrace is a debugging program which runs a specified command until the
command exits. While the command is executing, ltrace intercepts and
@ -39,6 +45,8 @@ execution of processes.
%patch0 -p1
%patch1 -p1
%patch2 -p1
%patch3 -p1
%patch4 -p1
%build
%configure --docdir=%{?_pkgdocdir}%{!?_pkgdocdir:%{_docdir}/%{name}-%{version}}
@ -63,6 +71,12 @@ echo ====================TESTING END=====================
%{_datadir}/ltrace
%changelog
* Fri Jan 10 2014 Petr Machata <pmachata@redhat.com> - 0.7.91-4
- Fix crashing a prelinked PPC64 binary which makes PLT calls through
slots that ltrace doesn't trace.
(ltrace-0.7.91-breakpoint-on_install.patch,
ltrace-0.7.91-ppc64-unprelink.patch)
* Thu Jan 9 2014 Petr Machata <pmachata@redhat.com> - 0.7.91-3
- Fix a problem in tracing across fork on PPC64
(ltrace-0.7.91-ppc64-fork.patch)