Skip to content
/ libpulp Public

libpulp enables live patching in user space applications.

License

Notifications You must be signed in to change notification settings

SUSE/libpulp

Repository files navigation

Libpulp

status

Table of contents

  1. Introduction
    1. Getting started
    2. License
    3. Known issues
  2. Contributing
    1. Coding style
    2. Project structure
  3. Consistency
  4. The patching process
  5. Description file syntax

Introduction

Libpulp is a framework that enables userspace live patching. It is composed of a library per se and a collection of tools used in the preparation of live-patchable libraries and in the application of live patches to running processes. In order to be live-patchable, a library must be compiled with patchable function entries1, but no changes to the library source-code are needed. Apart from that, processes must preload 2 libpulp.so in order to be live-patchable.

1 GCC provides the -fpatchable-function-entry option, which adds nop instructions to the prologue of functions. These nops are used by Libpulp to divert the execution flow when live patches are applied.

2 Live-patchable libraries do not explicitly require libpulp to be loaded at process initialization, instead, system administrators must start processes with LD_PRELOAD=libpulp.so, when they want to be able to live patch them.

Getting started

Building and running the test suite is probably the fastest way to get started with Libpulp. The build system is based on GNU autotools, so, from a fresh checkout of the repository, the typical build commands are enough:

./bootstrap
./configure
make
make check

The test suite, apart from avoiding unintentional regressions during development, provides several examples on how to use Libpulp. The oldest of the test cases, numserv.py, is also one of the easiest to understand: it starts the numserv application, which loads two live-patchable libraries, then interacts with it, applying live patches and checking the results. It is a good place to get started. Likewise, the parameters.py test case is also simple and a good starting point.

License

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.

Contributing

Contributions are welcome! You are welcome to open bug reports at the git hosting platform, submit patches through merge requests, or email any of them to our mailing list (https://lists.opensuse.org/ulp-devel).

Coding Style

The easiest way to adhere to the coding style is to use the following command, which will read the contents of the .clang-format file and apply the rules described in it to filename. However, notice that using this only makes sense with .c and .h files.

clang-format -style=file _filename_

The style is the same that is used in the GNU project, except that:

  1. Eight spaces DO NOT become a TAB character; TABs are avoided;
  2. Opening curly braces in control statements (if, while, switch, etc) remain on the same line; only curly braces on the first column, such as for function and struct definitions, need a newline;
  3. Opening parentheses are only preceded by a space in controls statements (if, while, switch, etc); not in function or macro calls;
  4. Cases in switch statements add one indentation level;
  5. Backslashes at the end of lines need not be aligned;
  6. Breaking lines either before or after operators are allowed.

Project structure

The directory hierarchy is divided into the following major components:

lib

This directory mostly contains the files that make-up libpulp.so, the library that programs must preload to become live-patchable processes. This library is built from just a few files: ulp.c, which contains the bulk of the live patching routines; ulp_prologue.S, which contains a routine that live patched functions call to save and restore registers; and ulp_interface.S, which contains the entry-point functions that the Trigger and Check tools use to apply and check live patches.

tools

The following tools comprise the tool set of Libpulp:

  • Post: This tool converts sequences of one-byte nops at the entry of patchable functions into multi-byte nops.

  • Packer: This tool integrates the live patch metadata out of a description file into a livepatch container (.so file). The description file syntax is described in its own section.

  • Trigger: This tool is used to apply or revert a livepatch, or a series of livepatches.

  • Check: This tool introspects into a target process and verifies if a given patch was applied.

  • Dump: This tool parses and dumps the contents of a live patch container.

  • Patches: This tool checks which processes has livepatching capabilities loaded, as well as showing which livepatches are applied.

  • Messages: This tool retrieves the libpulp.so internal messages.

tests

This directory contains everything related to the test suite, which can be execute with 'make check'. Python files are scripts that start the to-be-patched processes (preloading libpulp.so), apply live patches, and poke them to verify that their outputs changed accordingly. Files with the _livepatch suffix are built into the live patches per se. Everything else is test libraries and applications.

The patching process

A live patch is built on top of existing libraries. The Libpulp framework provides the tools to create the patches. Once the patch is created, it is applied through a ptrace-based tool, that will: attach to the target process; check if it is compliant with live patching; verify that the target library is properly loaded into its address space; through control-flow redirection, use routines from Libpulp to make the process load the new version of the functions to its address space; then, through adding detours to the old function prologues, ensure that only the new version of the functions are invoked.

The detours are not patched directly on top of previously existing functions. Instead, every function must be emitted by gcc with an area of padding nops which is then overwritten. Once overwritten, the new instructions transfer control to a selection routine, which has access to all live patches and re-transfers control to the currently active version of the target function (functions can be live patched multiple times, and live patches can be deactivated, which causes the previous version (or the baseline) to become active again).

Description file syntax

The live patching metadata is built from a description file which should be written with the following syntax:

1: <absolute path of .so with patching functions>
2: @<absolute path of the targeted library>
3: <old_fname_1>:<new_fname_1>
4: <old_fname_2>:<new_fname_2>
5: #<var1>:<var1ref>:<var1_offset>:<var1ref_offset>
6: #<var2>:<var2ref>:<var2_offset>:<var2ref_offset>
6: #%<tlsvar1>:<tlsvar1ref>:<tlsvar1_offset>:<tlsvar1ref_offset>
...

Line 1 provides the absolute path of a .so file that contains all the functions which will be used to replace functions in the running process. Line 2 provides the absolute path for the library that will be patched; it must be preceded by '@'. Lines 3 and 4 specify pairs of replaced and replacing functions (there could be more lines if more functions need replacing). Lines 5 and 6 specify the offsets that local (not-exported) variables in the target library have from the beginning of the library load location, as well as the offsets of references to those variables in the live patch object. If the local variable line contains a %, it indicates that it is a Thread Local Storage (TLS) variable. These offsets are used by Libpulp to enable access to local variables from the live patch.