Skip to content

Commit

Permalink
Factor-out x86_64 specific code
Browse files Browse the repository at this point in the history
Libpulp is deeply tied on x86_64 architecture. This commit factors out
its code and generalize some of its concepts in order to work in other
architectures.

Signed-off-by: gbelinassi <[email protected]>
  • Loading branch information
giulianobelinassi committed Oct 14, 2024
1 parent 50c4207 commit 60ecba7
Show file tree
Hide file tree
Showing 20 changed files with 429 additions and 378 deletions.
8 changes: 3 additions & 5 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ AC_INIT([libpulp],[0.3.6],[[email protected]])
AC_CONFIG_AUX_DIR([config])
AC_CONFIG_MACRO_DIRS([config])

# For multiarch builds.
AC_CANONICAL_TARGET

# Check for the availability of macros from autoconf-archive.
AC_MSG_CHECKING([autoconf-archive availability])
m4_ifndef([AX_CHECK_COMPILE_FLAG], [ax_available="no"])
Expand Down Expand Up @@ -223,15 +226,10 @@ AC_SUBST([PAGE_SIZE], [$($GETCONF PAGE_SIZE)]))
AC_SUBST([ULP_NOPS_LEN], [16])
AC_SUBST([PRE_NOPS_LEN], [14])

AC_SUBST([ULP_NOPS_LEN_ENDBR64], [$(expr $ULP_NOPS_LEN + 4)])
AC_SUBST([PRE_NOPS_LEN_ENDBR64], [$(expr $PRE_NOPS_LEN + 4)])

AC_DEFINE_UNQUOTED([ULP_NOPS_LEN], [$ULP_NOPS_LEN],
[Total number of padding nops])
AC_DEFINE_UNQUOTED([PRE_NOPS_LEN], [$PRE_NOPS_LEN],
[Padding nops before the entry point of functions])
AC_DEFINE_UNQUOTED([ULP_NOPS_LEN_ENDBR64], [$ULP_NOPS_LEN_ENDBR64],
[Total number of padding nops when endbr64 is issued])

AC_CONFIG_FILES([Makefile
include/Makefile
Expand Down
40 changes: 40 additions & 0 deletions include/arch/x86_64/arch_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#ifndef _ARCH_X86_64_H
#define _ARCH_X86_64_H

/** Intel endbr64 instruction optcode. */
#define INSN_ENDBR64 0xf3, 0x0f, 0x1e, 0xfa

/** Offset of TLS pointer. */
#define TLS_DTV_OFFSET 0

/** Struct used to store the registers in memory. */
typedef struct user_regs_struct registers_t;

/** Register in which the function stores the return value. */
#define FUNCTION_RETURN_REG(reg) ((reg).rax)

/** Register which acts as a program counter. */
#define PROGRAM_COUNTER_REG(reg) ((reg).rip)

/** Register which acts as top of stack. */
#define STACK_TOP_REG(reg) ((reg).rsp)

/** Set the GLOBAL ENTRYPOINT REGISTER, which in x86_64 doesn't exist. */
#define SET_GLOBAL_ENTRYPOINT_REG(reg, val)

/** Program load bias, which can be recovered by running `ld --verbose`. */
#define EXECUTABLE_START 0x400000UL

/** The red zone. */
#define RED_ZONE_LEN 128

/** File name of the dynamic loader. */
#define LD_LINUX "ld-linux-x86-64.so.2"

/**
* Number of bytes that the kernel subtracts from the program counter,
* when an ongoing syscall gets interrupted and must be restarted.
*/
#define RESTART_SYSCALL_SIZE 2

#endif
2 changes: 2 additions & 0 deletions include/ulp.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ int __ulp_apply_patch();
void __ulp_print();

/* functions */
void *memwrite(void *dest, const void *src, size_t n);

void free_metadata(struct ulp_metadata *ulp);

int unload_handlers(struct ulp_metadata *ulp);
Expand Down
1 change: 0 additions & 1 deletion include/ulp_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
* scenarios. */
#define ULP_METADATA_BUF_LEN (512 * 1024)
#define ULP_PATH_LEN 256
#define RED_ZONE_LEN 128

#define ARRAY_LENGTH(v) (sizeof(v) / sizeof(*(v)))

Expand Down
6 changes: 3 additions & 3 deletions lib/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ libpulp_la_SOURCES = \
interpose.c \
msg_queue.c \
error.c \
ulp_prologue.S \
ulp_interface.S
arch/$(target_cpu)/ulp_interface.S \
arch/$(target_cpu)/patch.c
libpulp_la_DEPENDENCIES= libpulp.versions
libpulp_la_LDFLAGS = \
-ldl \
Expand All @@ -36,6 +36,6 @@ libpulp_la_LDFLAGS = \

libpulp_la_LIBADD = $(top_builddir)/common/libcommon.la

AM_CFLAGS += -I$(top_srcdir)/include
AM_CFLAGS += -I$(top_srcdir)/include -I$(top_srcdir)/include/arch/$(target_cpu)

EXTRA_DIST = libpulp.versions
177 changes: 177 additions & 0 deletions lib/arch/x86_64/patch.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* libpulp - User-space Livepatching Library
*
* Copyright (C) 2017-2024 SUSE Software Solutions GmbH
*
* This file is part of libpulp.
*
* libpulp is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* libpulp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with libpulp. If not, see <http://www.gnu.org/licenses/>.
*/

#define _GNU_SOURCE

#include <stddef.h>
#include <string.h>

#include "config.h"
#include "error.h"
#include "msg_queue.h"
#include "ulp.h"
#include "arch/x86_64/arch_common.h"

/** Intel endbr64 instruction optcode. */
static const uint8_t insn_endbr64[] = {INSN_ENDBR64};

/* clang-format off */

/** Offset of the data entry in the ulp_prologue. */
#define ULP_DATA_OFFSET 6 // ------------------------------+
// |
static char ulp_prologue[ULP_NOPS_LEN] = { // |
// Preceding nops |
0xff, 0x25, 0, 0, 0, 0, // jmp 0x0(%rip) <-------+ |
0, 0, 0, 0, 0, 0, 0, 0, // <data> &__ulp_prolog | <-+
// Function entry is here |
0xeb, -(PRE_NOPS_LEN + 2) // jmp ----------------------+
// (+2 because the previous jump consumes 2 bytes.
};

#define ULP_NOPS_LEN_ENDBR64 (ULP_NOPS_LEN + 4)

/** Offset of the data entry in the ulp_prologue. */
static char ulp_prologue_endbr64[ULP_NOPS_LEN_ENDBR64] = {
// Preceding nops
0xff, 0x25, 0, 0, 0, 0, // jmp 0x0(%rip) <-------+
0, 0, 0, 0, 0, 0, 0, 0, // <data> &__ulp_prolog |
// Function entry is here |
INSN_ENDBR64, // endbr64 |
0xeb, -(PRE_NOPS_LEN + 2 + 4) // jmp ----------------------+
// (+2 because the previous jump consumes 2 bytes.
};
/* clang-format on */

/** @brief Write new function address into data prologue of `old_fentry`.
*
* This function replaces the `<data>` section in prologue `old_fentry`
* with a pointer to the new function given by `manager`, which will
* replace the to be patched function.
*
* @param old_fentry Pointer to prologue of to be replaced function
* @param manager Address of new function.
*/
void
ulp_patch_addr_absolute(void *old_fentry, void *manager)
{
char *dst = (char *)old_fentry + ULP_DATA_OFFSET;
memwrite(dst, &manager, sizeof(void *));
}

/** @brief Copy the ulp proglogue layout into the function to be patched's
* prologue
*
* This function copies the new code prologue into the old function prologue
* in order to redirect the execution to the new function.
*
*/
static void
ulp_patch_prologue_layout(void *old_fentry, const char *prologue, int len)
{
memwrite(old_fentry, prologue, len);
}

/** @brief skip the ulp prologue.
*
* When a function gets live patch, the nops at its entry point get replaced
* with a backwards-jump to a small segment of code that redirects execution to
* the new version of the function. However, when all live patches to said
* function are deactivated (because the live patches have been reversed), the
* need for the backwards-jump is gone.
*
* The following function replaces the backwards-jump with nops, thus making
* the target function look like it did at the beginning of execution, i.e.
* without live patches.
*
* @param fentry Address to write the prologue to.
*/
void
ulp_skip_prologue(void *fentry)
{
static const char insn_nop2[] = { 0x66, 0x90 };
int bias = 0;
if (memcmp(fentry, insn_endbr64, sizeof(insn_endbr64)) == 0)
bias += sizeof(insn_endbr64);

/* Do not jump backwards on function entry (0x6690 is a nop on x86). */
memwrite((char *)fentry + bias, insn_nop2, sizeof(insn_nop2));
}

/** @brief Actually patch the old function with the new function
*
* This function will finally patch the old function pointed by `old_faddr`
* with the one pointed by `new_faddr`, replacing the ulp NOP prologue with
* the intended content to redirect to the new function.
*
* @param old_faddr Address of the old function.
* @param new_faddr Address of the new function.
* @param enable False to disable the redirection to the new function.
*
* @return 0 if success, error code otherwise.
*/
int
ulp_patch_addr(void *old_faddr, void *new_faddr, int enable)
{
void *addr;

int ulp_nops_len;
const char *prologue;

const unsigned char *as_bytes = old_faddr;

/* Check if the first instruction of old_function is endbr64. In this
case, we have to handle things differently. */
if (memcmp(old_faddr, insn_endbr64, sizeof(insn_endbr64)) == 0) {
ulp_nops_len = ULP_NOPS_LEN_ENDBR64;
prologue = ulp_prologue_endbr64;
as_bytes += sizeof(insn_endbr64);
}
else {
ulp_nops_len = ULP_NOPS_LEN;
prologue = ulp_prologue;
}

/* Check if we have the two NOP sequence or a JMP ref8 insn. Else we might
be attempting to patch a non-livepatchable function. */

if (!(as_bytes[0] == 0xEB ||
(as_bytes[1] == 0x90 &&
(as_bytes[0] == 0x90 || as_bytes[0] == 0x66)))) {
WARN("Function at addr %lx is not livepatchable",
(unsigned long)old_faddr);
return ENOPATCHABLE;
}

/* Find the starting address of the pages containing the nops. */
addr = old_faddr - PRE_NOPS_LEN;

/* Actually patch the prologue. */
if (enable) {
ulp_patch_prologue_layout(addr, prologue, ulp_nops_len);
ulp_patch_addr_absolute(addr, new_faddr);
}
else {
ulp_skip_prologue(old_faddr);
}

return 0;
}
2 changes: 1 addition & 1 deletion lib/ulp_interface.S → lib/arch/x86_64/ulp_interface.S
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* libpulp - User-space Livepatching Library
*
* Copyright (C) 2017-2021 SUSE Software Solutions GmbH
* Copyright (C) 2017-2024 SUSE Software Solutions GmbH
*
* This file is part of libpulp.
*
Expand Down
4 changes: 2 additions & 2 deletions lib/interpose.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "msg_queue.h"
#include "ulp.h"
#include "ulp_common.h"
#include "arch_common.h"

/* This header should be included last, as it poisons some symbols. */
#include "error.h"
Expand Down Expand Up @@ -393,8 +394,7 @@ get_ld_global_locks()
const char *tok;
int major, minor;

void *rtld_global =
get_loaded_symbol_addr("ld-linux-x86-64.so.2", "_rtld_global", NULL);
void *rtld_global = get_loaded_symbol_addr(LD_LINUX, "_rtld_global", NULL);
if (!rtld_global) {
libpulp_crash("symbol _rtld_global not found in ld-linux-x86_64.so\n");
}
Expand Down
Loading

0 comments on commit 60ecba7

Please sign in to comment.