Skip to content

Instantly share code, notes, and snippets.

@lpereira
Last active January 29, 2023 20:12
Show Gist options
  • Save lpereira/5062388 to your computer and use it in GitHub Desktop.
Save lpereira/5062388 to your computer and use it in GitHub Desktop.
Partial functions in C This program illustrates a hack to create partial functions in C. The way it works is that it generates a template function (partial_template_function) with known pointers, that is later copied to a region of memory obtained with mmap(), patched up with the address and data to be passed to the real function, and then made …
/*
* Partial applied functions in C
* Leandro Pereira <[email protected]>
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/mman.h>
struct Partial {
void *caller;
size_t caller_len;
};
#define PARAMETER_CONSTANT 0xFEEDBEEF
#define FUNCTION_CONSTANT 0xABAD1DEA
static bool
patch_pointer(void *code_addr, size_t code_len, void *look_for, void *patch_with)
{
unsigned char *code = code_addr;
intptr_t look = (intptr_t)look_for;
do {
if (*((intptr_t *)code) == look) {
union {
unsigned char octet[sizeof(void *)];
void *ptr;
} patch;
patch.ptr = patch_with;
code[0] = patch.octet[0];
code[1] = patch.octet[1];
code[2] = patch.octet[2];
code[3] = patch.octet[3];
return true;
}
code++;
} while (code_len--);
return false;
}
/*
* Make sure this function is always declared before partial_new().
*/
static void
partial_template_function(void)
{
((void (*)(void *))FUNCTION_CONSTANT)((void *)PARAMETER_CONSTANT);
}
/*
* Make sure no functions get declared here.
*/
struct Partial *
partial_new(void (*func)(void *data), void *data)
{
struct Partial *t;
if (!func) return NULL;
t = calloc(1, sizeof(*t));
if (!t) return NULL;
t->caller_len = (size_t)((intptr_t)partial_new - (intptr_t)partial_template_function);
assert(t->caller_len > 0);
/*
* Map it as RW, without EXEC bits, and only turn it on after pointers
* have been patched up
*/
t->caller = mmap(0, t->caller_len, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (t->caller == MAP_FAILED) goto no_mmap;
/*
* Copy the code the compiler generated to call an arbitrary function with
* an arbitrary parameter
*/
memcpy(t->caller, partial_template_function, t->caller_len);
/*
* Change the arbitrary constants to the ones passed as arguments to this
* function. This thing will only work on 32-bit, little endian
*/
if (!patch_pointer(t->caller, t->caller_len, (void *)FUNCTION_CONSTANT, func)) goto no_patch;
if (!patch_pointer(t->caller, t->caller_len, (void *)PARAMETER_CONSTANT, data)) goto no_patch;
/* Make sure no one can change this later, and that we can execute it */
mprotect(t->caller, t->caller_len, PROT_EXEC | PROT_READ);
return t;
no_patch:
munmap(t->caller, t->caller_len);
no_mmap:
free(t);
return NULL;
}
void
partial_del(struct Partial *t)
{
if (!t) return;
munmap(t->caller, t->caller_len);
free(t);
}
void
(*partial_to_function(struct Partial *t))()
{
if (!t) return NULL;
return (void (*)(void *))(t)->caller;
}
static void
test(void *data)
{
printf("it worked! received data: %p\n", data);
}
static void
another_test(void *data)
{
printf("another test with different data: %p\n", data);
}
int main(void)
{
struct Partial *t, *t2;
void (*func)();
void (*func2)();
t = partial_new(test, (void *)0x12341337);
if (!t) goto end;
t2 = partial_new(another_test, (void *)0xbebac0ca);
if (!t2) goto end;
func = (void *)partial_to_function(t);
if (!func) goto end;
func2 = partial_to_function(t2);
if (!func2) goto end;
/*
* Now pass `func` around as a callback. When it is called, the
* parameter passed to partial_new() will be passed to the original
* function. Magic.
*/
atexit(func);
/* Or call it directly. */
func2();
/*
* atexit() callbacks are called in the dtor, so make sure partial_del(t)
* does not run.
*/
partial_del(t2);
end:
return 0;
}
@bherrera
Copy link

bherrera commented Jul 4, 2015

Nice trick.
I don't see the point/need of this cast inside partial_to_function:

return (void (*)(void *))(t)->caller;

if you keep the cast shouldn't it be:

return (void (*)())(t)->caller;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment