{"id":16607549,"url":"https://github.com/davidgraeff/userspace-runtime-patching-cpp","last_synced_at":"2025-04-19T14:39:01.163Z","repository":{"id":136770856,"uuid":"211028015","full_name":"davidgraeff/userspace-runtime-patching-cpp","owner":"davidgraeff","description":"A proof of concept C++ targeting runtime patch library","archived":false,"fork":false,"pushed_at":"2019-09-26T10:19:51.000Z","size":167,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-29T08:43:40.270Z","etag":null,"topics":["hot-patch","userspace"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidgraeff.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-26T07:31:20.000Z","updated_at":"2024-11-29T11:18:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"b1ca8bf7-c15a-4501-acfa-d32de904b0e5","html_url":"https://github.com/davidgraeff/userspace-runtime-patching-cpp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidgraeff%2Fuserspace-runtime-patching-cpp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidgraeff%2Fuserspace-runtime-patching-cpp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidgraeff%2Fuserspace-runtime-patching-cpp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidgraeff%2Fuserspace-runtime-patching-cpp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidgraeff","download_url":"https://codeload.github.com/davidgraeff/userspace-runtime-patching-cpp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249715925,"owners_count":21315059,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hot-patch","userspace"],"created_at":"2024-10-12T01:23:05.863Z","updated_at":"2025-04-19T14:39:01.156Z","avatar_url":"https://github.com/davidgraeff.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Live Patching Demonstration\n\nHot patching, also known as live patching or dynamic software updating, is the application of patches without\nshutting down and restarting the system or the program concerned.\nThis addresses problems related to unavailability of service provided by the system or the program.\n\nThere are trivial solutions for certain kinds of applications to archive zero-downtime as described in the next section. \nA more involved way of hot-patching rewrites instructions in hot / loaded memory pages.\n\nThis repository contains a proof of concept implementation of a library targeting C ABI and C++ (Italium) ABI that\nfavours the idea of a binary patch repository / registry.\nRead on for a bit of a context or skip right to the [Function patching](#function-patching) section.\n\nTable of Contents\n=================\n\n* About hot-patching\n  * [Zero-downtime full replacement](#zero-downtime-full-replacement)\n  * [dlopen patching](#dlopen-patching)\n  * [Function patching](#function-patching)\n    * [OS, compiler and machine dependant](#os-compiler-and-machine-dependant)\n    * [Notes on C   specifics](#notes-on-c-specifics)\n    * [Compiler optimizations](#compiler-optimizations)\n * [About this library](#about-this-library)\n* [How to use](#how-to-use)\n* [Build](#build)\n* [Tests and Documentation](#tests-and-documentation)\n* [License](#license)\n\n\n### Zero-downtime full replacement\nThe probably most safe live-patching method is to embed support for a \"soft\" restart, so replacing the entire\napplication without downtime.\nAn external signal (might literally be a [unix] *signal* like USR1) notifies the binary to start the newer version\nwhich waits for a resource (file descriptors) handover.\nThat handover might happen via a unix domain socket and an SCM_RIGHTS type of message (see http://man7.org/linux/man-pages/man3/cmsg.3.html).\nThis is also known as socket migration which [B. Kuntz and K. Rajan](https://www.cs.cmu.edu/~softagents/migsock/MIGSOCK.pdf)\nprovide an in-deep overview of.\n\nExample: Let's imagine our process is a web server. The current version will finish serving the open requests and shuts down when done.\nWhile the new version got the socket handler for incoming requests and will already start to serve new requests.\n\nThis trivia patching method is not handled in this repository.\n\n### dlopen patching\n\nIf an application is designed in a way that all functionality is loaded at runtime via dynamic libraries,\nit is a no-brainer to [`dlopen`](https://linux.die.net/man/3/dlopen) a new variant of a library and swap\nfunction pointers with the newly loaded symbols.\n\nThis is also a trivial method and is thread safe, because pointer swapping happens atomically.\n(This is not thread safe if multiple methods need to be replaced and patches have mutable dependencies.)\n\nThe next patching method is able to hot patch statically linked code, but uses `dlopen` internally as well. \n\n### Function patching\n\nFunction patching rewrites executable memory pages. If no compiler support for function padding is available,\nthe first few instructions of the to-be-patched method are overwritten with an unconditional jump instruction.\nThe 32bit offset jump instruction is five bytes in length, the 64bit offset `jmp` instruction is 14 bytes in length.\nThis minimal length imposes the same same minimal length for our target method.\n\n```asm\nEB cb\tJMP rel8\tJump short, relative, displacement relative to next instruction.\nE9 cw\tJMP rel16\tJump near, relative, displacement relative to next instruction.\nE9 cd\tJMP rel32\tJump near, relative, displacement relative to next instruction.\n```\n\nIf that precondition is not hold or the function signature differs, a live-patch cannot be performed.\n\n**Runtime implications:**\nThe trade-off of security / stability through patchable methods vs runtime speed has to be considered.  \nThis method adds an indirection and enforces inlining to be off for patchable functions.\n\n#### OS, compiler and machine dependant\nThis patching method is machine specific and also operating system specific.\n\n* The method used here is tailored for Linux and x86-64.\n\n* Compiler may support hot-patching like the Microsoft VS compiler. It prepends functions with 5 bytes of NOPs\n(No Operation) and the first instruction of a function is a `mov edi, edi` instruction (2 bytes NO OP). \nx86 is a variable length instruction set. The first two NO-OP bytes can be replaced by a [short 2-byte relative jump](https://thestarman.pcministry.com/asm/2bytejumps.htm),\nbringing us into the 5 bytes of NOP section where a full unconditional jump can be used.\n\nG++ does not have a similar support so far and without further care we do destroy the first part of the original function\nand the write cannot be performed atomic because it consists of 5 bytes. Atomic writes would either be a byte, a word or a dword.\n\n* Operating systems usually load executable code (the .text section of the binary) into read-only memory pages.\nFor patching those memory pages, operating system specific syscalls must be performed.\nOn linux this is [`mprotect`][2].\n\n[1]: https://thestarman.pcministry.com/asm/2bytejumps.htm\n[2]: http://man7.org/linux/man-pages/man2/mprotect.2.html\n\n#### Notes on C++ specifics\n\nPatching a C++ class method is not that different to a C function patch.\nThe calling convention need to be known, which is usually [`thiscall`][3]:\nThe caller cleans the stack (no work for our patched function), parameters are pushed right-to-left to the stack.\nThe `this` pointer is assigned to the ECX register. \n\nIn contrast to C, C++ does not have a standardized ABI though. Name mangling is different across different compilers.\nCompilers have the freedom of laying out the virtual table however they want and yes, the calling convention can be different\nas well. The implemented virtual function patch is g++ specific.\n\nIf a binary is target that has been compiled with different compiler flags or a different compiler, this\nlibrary will **certainly crash** or rather cause **undefined behaviour**.\n\n[3]: https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html\n\n#### Compiler optimizations \n\nCompilers will usually eagerly inline if `-O` != 0 (any release build) which would break our live-patching mechanism.\nThe library provides a `FORCE_NO_INLINE` macro that must be used in front of each live-patchable method.\n**Note**: Forcing no-inline is not standardized yet and might not work on all compilers.  \n\nAdditionally the demo application adds the `-flive-patching=inline-only-static` [compiler flag][1] on g++ 9.1 and newer.\n\n[1]: https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=126dab7c9d84294f256b1f7bf91c24a9e7103249\n\n### About this library \n\nOrganisational:\nThis repository is split into a demonstration application and the patching library itself.\nWhen build, the build system will also already compile the demonstrational patches. \n\nHow does it work:\n\n![Registry Patch Flow](./doc/arch.png)\n\n1. The patching library will, on request, contact a patch registry (which happens to be the `registry/meta.json` file in this repo)\nto retrieve meta information about new available patches. There are of course patches available.\n\n2. Because binary blobs downloaded off the internet is not the best of ideas, this repo does not contain pre-build x86 patches\n   but build-system compiled ones. Those patches are object files, linked into dynamically linked libraries.\n\n3. The registry meta information contains the functions mangled name (symbol name).\u003cbr\u003e\n   Note: In theory the object file could be used directly, but to save us from aligning functions, patching addresses (for `-fpic` compiled code),\n   initializing global variables and all that jazz that the operating systems loader already does for us, `dlopen` and `dlsym` are used. \n\n4. `dlsym` has provided us with the functions address.\n   The affected memory page of the to-be-replaced function is made writable and the first few instructions of that function\n   are replaced with an unconditional jump to the `dlsym` provided address. This works for C and C++ non-member as well as\n   member functions.\n   \n5. For virtual member functions, the objects vtable is patched instead.\n\nPatches are only applied if the integer based version of a patch is higher than a potentially already patched function version.\nTo make this work, those version numbers are stored in the P_TABLE.\n\nDisclaimer: In contrast to a real-world scenario, the demo application patches itself and keeps a list of patchable function addresses in memory (`P_TABLE`).\nThat simplifies this demonstration:\n\n* Because no privileged access rights need to be granted to change non-process-owned memory pages.\n* It is assumed that any other user-space patchable binary is also either instrumented or accompanied by a static symbol address table.\n  Because of Address space layout randomization which is enabled by default, the later option is of no use however.\n\n## How to use\n\nThere is no command line interface, but the app accepts key inputs.\n\n* \"u\": Press u to just update the registry cache.\n* \"p\": Press p to patch functions to their newest versions.\n       If the registry cache is too old, it will be refreshed first.\n\nAfter patching the stdout output should change like in this excerpt:\n\n```shell script\nPress u for updating the registry. Press p for patching. Press c for canceling.\nCurrent working dir: /home/david/CLionProjects/runtime-patching\n\nHello, from C++ member function! 42\nHello, from C function! 42\n\n\u003e p\n  Patching now\n  Patching _ZN9DemoClass9say_helloEiSt17basic_string_viewIcSt11char_traitsIcEE to 1\n  Patched _ZN9DemoClass9say_helloEiSt17basic_string_viewIcSt11char_traitsIcEE to 1\n  Patching _Z13say_hello_funiSt17basic_string_viewIcSt11char_traitsIcEE to 1\n  Patched _Z13say_hello_funiSt17basic_string_viewIcSt11char_traitsIcEE to 1\n\nCompletely patched! from C++ member function! 42\nCompletely patched! from C function! 42\n\n```\n\nSecurity frameworks and techniques like seccomp, SELinux and Apparmor might prevent the required `mprotect` kernel call\nin which case the application gracefully crashes.\n\n## Build\n\nThe buildsystem is cmake. This repository is almost self-contained with the exception of the test framework.\nAll external libraries are provided in `vendor` directories and are BSD or MIT licensed.\n\nRun `mkdir build \u0026\u0026 cd build` and `cmake ../ \u0026\u0026 cmake --build .` to build the project.\nYou need a compiler that supports C++17.\n\n## Tests and Documentation\n\nUse `cmake test` on the command line to start the test suite.\nGoogle Test is used and will be downloaded and compiled if the libraries are not available in PATH.\n\nThe library is documented in a Doxygen compatible format.\nIf doxygen is installed, use `cmake --build . --target doc` in the build directory.\n\n## Limitations\n\n* Right now this library only adds patches (`dlopen`), but never removes (`dlclose`) non used ones.\n  Either dlopens own reference count could be used (so that for each `Patch` destructor `dlclose` is called)\n  or a DlOpen RAII shared pointer class is created. \n* The registry is a proof-of-concept local file, no checksum, no auth implementation\n\n## License\n\nMIT\n\nDavid Gräff - 2019","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidgraeff%2Fuserspace-runtime-patching-cpp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidgraeff%2Fuserspace-runtime-patching-cpp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidgraeff%2Fuserspace-runtime-patching-cpp/lists"}