Skip to content

Commit

Permalink
Revert "Remove insn_queue from libpulp"
Browse files Browse the repository at this point in the history
Relying on /proc/self/mem to bypass mprotect issues turn out to be
unreliable: the process can change into another user which does not
have access to this file, hence ptrace as `ulp` being root is the
only way out.

This reverts commit 795c590.
  • Loading branch information
giulianobelinassi committed Oct 17, 2024
1 parent 5dc0d46 commit 297c34d
Show file tree
Hide file tree
Showing 10 changed files with 571 additions and 49 deletions.
3 changes: 2 additions & 1 deletion include/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ noinst_HEADERS = \
error_common.h \
terminal_colors.h \
ld_rtld.h \
insn_queue.h
insn_queue.h \
insn_queue_lib.h

# Workaround a bug in Autoconf 2.69
if CPU_X86_64
Expand Down
35 changes: 35 additions & 0 deletions include/insn_queue_lib.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* libpulp - User-space Livepatching Library
*
* Copyright (C) 2023 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/>.
*/

#ifndef INSNQ_TOOL_H
#define INSNQ_TOOL_H

#include "insn_queue.h"

void *insnq_get_writable_area(insn_queue_t *, size_t insn_size);

ulp_error_t insnq_insert_print(const char *string);

ulp_error_t insnq_insert_write(void *addr, int n, const void *bytes);

int insnq_ensure_emptiness(void);

#endif
4 changes: 4 additions & 0 deletions lib/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ libpulp_la_SOURCES = \
ulp.c \
interpose.c \
msg_queue.c \
insn_queue.c \
error.c

libpulp_la_LDFLAGS = \
Expand Down Expand Up @@ -56,4 +57,7 @@ libpulp_la_LIBADD = $(top_builddir)/common/libcommon.la

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

# Add -fno-strict-alias to the insn_queue code.
insn_queue.lo : CFLAGS += -fno-strict-aliasing

EXTRA_DIST = libpulp.versions
172 changes: 172 additions & 0 deletions lib/insn_queue.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* libpulp - User-space Livepatching Library
*
* Copyright (C) 2021 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/>.
*/

#include "insn_queue.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "error.h"
#include "ulp_common.h"

/** Global instruction queue object. */
insn_queue_t __ulp_insn_queue = { .version = INSNQ_CURR_VERSION };

static int
align_to(int val, int bytes)
{
int mask = bytes - 1;
return (val + mask) & (~mask);
}

/** @brief Get memory area to write an instruction to in the queue.
*
* This function will retrieve an area of memory in the queue object in which
* an instruction of size `msg_size` can be writen to. The instruction is
* appended to the end of the queue, and depending of the queue attribute
* `discard_old_content` it may return NULL if there is pending operations in
* the queue, or overwrite the instruction that is on the begining of the
* queue. In case the instruction do not fit in the queue, NULL is returned.
*
* @param queue The instruction queue object.
* @param msg_size Size of instruction to allocate area to.
*
* @return Valid pointer to write to in success, NULL otherwise.
*/
void *
insnq_get_writable_area(struct insn_queue *queue, size_t msg_size)
{
/* Write the msg_queue values in variables for briefness. */
int num_insns = queue->num_insns;
int size = queue->size;
char *buffer = queue->buffer;

/* In case the message is empty or it is too large for the buffer, don't
* bother even trying to insert it. */
if (msg_size == 0)
return NULL;

/* In case the instruction won't fit the queue, then quickly return with
NULL as answer. */
if (msg_size + size > INSN_BUFFER_MAX) {
return NULL;
}

/* Reserve area for write. This breaks strict aliasing rules, so this file
must be compiled with -fno-strict-aliasing. */
void *ret = &buffer[size];

/* Update number of bytes. */
size += msg_size;
num_insns++;

/* Commit back to original object. */

queue->num_insns = num_insns;
queue->size = size;

return ret;
}

/** @brief Insert print instruction into the queue.
*
* @param queue The instruction queue object.
* @param string String to print.
*/
ulp_error_t
insnq_insert_print(const char *string)
{
insn_queue_t *queue = &__ulp_insn_queue;

int string_size = strlen(string) + 1;
int insn_size = align_to(sizeof(struct ulp_insn_print) + string_size, 4);
struct ulp_insn_print *insn = insnq_get_writable_area(queue, insn_size);

if (insn == NULL) {
set_libpulp_error_state(EINSNQ);
return EINSNQ;
}

insn->base.type = ULP_INSN_PRINT;
insn->base.size = insn_size;
memcpy(insn->bytes, string, string_size);

return ENONE;
}

/** @brief Insert write instruction into the queue.
*
* @param queue The instruction queue object.
* @param addr Address to patch.
* @param n Number of bytes to patch.
* @param bytes Bytes to patch with.
*/
ulp_error_t
insnq_insert_write(void *addr, int n, const void *bytes)
{
insn_queue_t *queue = &__ulp_insn_queue;

int insn_size = align_to(sizeof(struct ulp_insn_write) + n, 8);
struct ulp_insn_write *insn = insnq_get_writable_area(queue, insn_size);

if (insn == NULL) {
set_libpulp_error_state(EINSNQ);
return EINSNQ;
}

insn->base.type = ULP_INSN_WRITE;
insn->base.size = insn_size;
insn->n = n;
insn->address = (uintptr_t)addr;
memcpy(insn->bytes, bytes, n);

return ENONE;
}

/** @brief Ensure that the instruction queue is empty.
*
* When a livepatch is triggered, the instruction queue must be empty in order
* to safely insert instructions on it. Otherwise, this means something bad
* occured on ulp side which prevented the queue to be updated after the insns
* were executed.
*
* This function will block livepatching if not empty.
*
* @return 0 if success, anything else if not empty
*
*/
int
insnq_ensure_emptiness(void)
{
insn_queue_t *queue = &__ulp_insn_queue;

if (queue->num_insns > 0 || queue->size > 0) {
WARN("WARN: instruction queue not empty. This is an indication that "
"something went wrong on ulp side.");

set_libpulp_error_state(EINSNQ);
return 1;
}

return 0;
}
38 changes: 15 additions & 23 deletions lib/ulp.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

#include "config.h"
#include "error.h"
#include "insn_queue_lib.h"
#include "interpose.h"
#include "msg_queue.h"
#include "ulp.h"
Expand Down Expand Up @@ -64,7 +65,8 @@ begin(void)
*
* The process may be launched with mprotect through seccomp, which
* will block certain addresses to be written. This function
* circunvent this by writing through /proc/self/map.
* circunvent this by queuing up an instruction to the ulp insn queue
* for the `ulp` tool to process later.
*
* @param dest Destination address
* @param src Source address
Expand All @@ -74,21 +76,11 @@ begin(void)
void *
memwrite(void *dest, const void *src, size_t n)
{
FILE *file = fopen("/proc/self/mem", "r+");

/* SLE have some processes which chroots into /proc. If the above fopen
fails then try this to check if this is the case. */
if (file == NULL) {
file = fopen("/self/mem", "r+");
libpulp_assert(file != NULL);
error_t e = insnq_insert_write(dest, n, src);
if (e != ENONE) {
return NULL;
}

libpulp_assert(fseek(file, (size_t)dest, SEEK_SET) == 0);
libpulp_assert(fwrite(src, 1, n, file) == n);

fflush(file);
fclose(file);

return dest;
}

Expand Down Expand Up @@ -160,6 +152,10 @@ __ulp_revert_patches_from_lib()
if (libpulp_is_in_error_state())
return get_libpulp_error_state();

/* If the instruction queue is in an weird state, we cannot continue. */
if (insnq_ensure_emptiness())
return get_libpulp_error_state();

/*
* If the target process is busy within functions from the malloc or
* dlopen implementations, applying a live patch could lead to a
Expand All @@ -172,10 +168,6 @@ __ulp_revert_patches_from_lib()
/* Otherwise, try to apply the live patch. */
result = revert_all_patches_from_lib(__ulp_metadata_buffer);

/* If we entered in an error state, then return the error. */
if (libpulp_is_in_error_state())
return get_libpulp_error_state();

/*
* Live patching could fail for a couple of different reasons, thus
* check the result and return either zero for success or one for
Expand All @@ -194,6 +186,10 @@ __ulp_apply_patch()
if (libpulp_is_in_error_state())
return get_libpulp_error_state();

/* If the instruction queue is in an weird state, we cannot continue. */
if (insnq_ensure_emptiness())
return get_libpulp_error_state();

/*
* If the target process is busy within functions from the malloc or
* dlopen implementations, applying a live patch could lead to a
Expand All @@ -206,10 +202,6 @@ __ulp_apply_patch()
/* Otherwise, try to apply the live patch. */
result = load_patch();

/* If we entered in an error state, then return the error. */
if (libpulp_is_in_error_state())
return get_libpulp_error_state();

/*
* Live patching could fail for a couple of different reasons, thus
* check the result and return either zero for success or whatever
Expand Down Expand Up @@ -1121,7 +1113,7 @@ push_new_detour(unsigned long universe, unsigned char *patch_id,

detour = calloc(1, sizeof(struct ulp_detour));
if (!detour) {
WARN("Unable to acllocate memory for ulp detour");
WARN("Unable to allocate memory for ulp detour");
return 0;
}

Expand Down
28 changes: 18 additions & 10 deletions tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ check_PROGRAMS = \
pcqueue \
comments \
block_mprotect \
insn_queue \
chroot \
visibility

Expand Down Expand Up @@ -562,16 +563,6 @@ dlsym_SOURCES = dlsym.c
dlsym_LDADD = -lpthread -ldl -lrt
dlsym_DEPENDENCIES = $(POST_PROCESS) $(METADATA)

# Workaround a bug in Autoconf 2.69
if CPU_X86_64
dlsym_LDADD += \
-l:ld-linux-x86-64.so.2
endif
if CPU_PPC64LE
dlsym_LDADD += \
-l:ld64.so.2
endif

stress_SOURCES = stress.c
stress_LDADD = libstress.la
stress_DEPENDENCIES = $(POST_PROCESS) $(METADATA)
Expand All @@ -589,6 +580,22 @@ block_mprotect_SOURCES = block_mprotect.c
block_mprotect_CFLAGS = $(AM_CFLAGS) -I/usr/include/libseccomp/
block_mprotect_LDADD = -lseccomp

insn_queue_SOURCES = insn_queue.c
insn_queue_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/../include -I$(srcdir)/../tools/include
insn_queue_LDADD =

# Workaround a bug in Autoconf 2.69
if CPU_X86_64
dlsym_LDADD += \
-l:ld-linux-x86-64.so.2
insn_queue_CFLAGS += -I$(srcdir)/../include/arch/x86_64/
endif
if CPU_PPC64LE
dlsym_LDADD += \
-l:ld64.so.2
insn_queue_CFLAGS += -I$(srcdir)/../include/powerpc64le/x86_64/
endif

chroot_SOURCES = chroot.c
chroot_CFLAGS = $(AM_CFLAGS)
chroot_LDADD = libparameters.la
Expand Down Expand Up @@ -648,6 +655,7 @@ TESTS = \
mprotect_patch.py \
patches.py \
mprotect_patch.py \
insn_queue.py \
chroot.py \
visibility.py

Expand Down
Loading

0 comments on commit 297c34d

Please sign in to comment.