Compare commits

12 커밋

작성자 SHA1 메시지 날짜
73dae3a102 append_syscall_to_bpf(): Check for unlikely case of too many sock_filters 2022-03-17 15:17:28 +01:00
f2ca26010a exile.hpp: Mark do_clone inline, not static 2022-03-15 08:48:04 +01:00
0f39ee7061 Makefile: Build exile.o separately, link it in all tests 2022-03-15 08:48:04 +01:00
41bd6e8f10 exile.h: Retire static child_read/write_pipe vars 2022-03-15 08:48:04 +01:00
7f083909e6 exile.h: Move definitions to new file exile.c
Especially with exile_launch(), we will be included
from more than one translation unit. Thus, ODR becomes
a headache now.

So move definitions to exile.c.
2022-03-15 08:48:04 +01:00
732623fc6f exile.h: Add extern "C" guards 2022-03-15 08:48:04 +01:00
dcfbe641f9 c++: Add explicit exile_launch() std::basic_string variant 2022-03-15 08:48:04 +01:00
72a3b041d9 c++: Retire exile_launch_trivial(), use std::enable_if 2022-03-15 08:48:04 +01:00
c57ba807d7 Makefile: Add 'tests' target, depend on headers too to rebuild on changes of those 2022-03-15 08:48:04 +01:00
6f19c53acf test.sh: Also run C++ tests 2022-03-15 08:48:04 +01:00
99d26480d7 Add test.cpp to test C++ API 2022-03-15 08:48:04 +01:00
f13cff754c Begin C++ API: Add exile.hpp with exile_launch() wrappers 2022-03-15 08:48:04 +01:00
7개의 변경된 파일2207개의 추가작업 그리고 1739개의 파일을 삭제

파일 보기

@ -1,17 +1,27 @@
prefix = /usr/local
bindir = $(prefix)/bin
CFLAGS = -std=c99 -Wall -Wextra -pedantic
CXXFLAGS = -std=c++20 -Wall -Wextra -pedantic
.DEFAULT_GOAL := test
.DEFAULT_GOAL := tests
clean:
rm -f test
rm -f test exile.o testcpp
test: test.c
$(CC) test.c -g $(CFLAGS) -o test
check: test
exile.o: exile.c exile.h
$(CC) -c exile.c -g $(CFLAGS) -o exile.o
test: test.c exile.h exile.o
$(CC) test.c exile.o -g $(CFLAGS) -o test
testcpp: test.cpp exile.h exile.hpp exile.o
$(CXX) test.cpp exile.o -g $(CXXFLAGS) -o testcpp
tests: test testcpp
check: tests
./test.sh
.PHONY: check

1854
exile.c Normal file

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

1757
exile.h

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

199
exile.hpp Normal file
파일 보기

@ -0,0 +1,199 @@
#include "exile.h"
#include <functional>
#include <iostream>
#include <string>
#include <tuple>
#include <memory>
#include <sys/wait.h>
#ifndef EXILE_MMAP_SIZE
#define EXILE_MMAP_SIZE 128 * 1024 * 1024 //128MB
#endif
template<typename T, typename U, typename ... Args>
class launch_arg
{
static_assert(std::is_trivially_copyable_v<T>);
static_assert(!std::is_pointer_v<T>);
public:
struct exile_policy *policy;
T *result_shm;
U fn;
std::tuple<Args...> args;
launch_arg(struct exile_policy *policy, T *result_shm, U fn, Args && ... args) : policy(policy),
result_shm(result_shm), fn(fn), args(std::forward<Args>(args)...) {}
};
template<typename T, typename U, typename ... Args>
class launch_arg_serializer
{
static_assert(std::is_copy_constructible_v<T>);
public:
struct exile_policy *policy;
char *serialize_buffer;
size_t n;
U fn;
std::tuple<Args...> args;
const std::function<size_t (const T &, char *, size_t n)> &serializer;
const std::function<T(const char * buf, size_t n)> &deserializer;
launch_arg_serializer(struct exile_policy *policy, char *serialize_buffer, size_t n, const std::function<size_t (const T &, char *, size_t)> &serializer, const std::function<T(const char *, size_t)> &deserializer, U fn, Args && ... args) : policy(policy), serialize_buffer(serialize_buffer), n(n), fn(fn), args(std::forward<Args>(args)...), serializer(serializer), deserializer(deserializer) {}
};
template<typename T, typename U, typename ... Args>
int exile_clone_handle_trivial(void * arg)
{
static_assert(std::is_trivially_copyable_v<T>);
static_assert(!std::is_pointer_v<T>);
launch_arg<T, U, Args...> *launchargs = (launch_arg<T, U, Args...> *) arg;
int ret = exile_enable_policy(launchargs->policy);
if(ret != 0)
{
EXILE_LOG_ERROR("exile_enable_policy() failed: %s\n", strerror(errno));
return 1;
}
T result = std::apply(launchargs->fn, launchargs->args);
std::cout << result;
memcpy(launchargs->result_shm, &result, sizeof(T));
return 0;
}
template<typename T, typename U, typename ... Args>
int exile_clone_handle_serializer(void * arg)
{
static_assert(std::is_copy_constructible_v<T>);
launch_arg_serializer<T, U, Args...> *launchargs = (launch_arg_serializer<T, U, Args...> *) arg;
int ret = exile_enable_policy(launchargs->policy);
if(ret != 0)
{
EXILE_LOG_ERROR("exile_enable_policy() failed: %s\n", strerror(errno));
return 1;
}
T result = std::apply(launchargs->fn, launchargs->args);
/* TODO: exception handling */
/* TODO: ugly :S */
char *target = launchargs->serialize_buffer + sizeof(size_t);
size_t n = launchargs->n - sizeof(size_t);
size_t size = launchargs->serializer(result, target, n);
memcpy(launchargs->serialize_buffer, &size, sizeof(size_t));
return 0;
}
inline int do_clone(int (*clonefn)(void *), void *launcharg)
{
struct rlimit rlimit;
int ret = getrlimit(RLIMIT_STACK, &rlimit);
if(ret != 0)
{
EXILE_LOG_ERROR("Failed to get stack size: %s\n", strerror(errno));
return ret;
}
size_t size = rlimit.rlim_cur;
char *stack = (char *) calloc(1, size);
if(stack == NULL)
{
EXILE_LOG_ERROR("Failed to allocate stack memory for child\n");
return 1;
}
stack += size;
ret = clone(clonefn, stack, 17 /* SIGCHLD */, launcharg);
int status = 0;
waitpid(ret, &status, __WALL);
if(WIFEXITED(status))
{
return WEXITSTATUS(status);
}
/* TODO: exception or what? */
return 23;
}
template<typename T, typename U, typename ... Args>
typename std::enable_if_t<std::is_trivially_copyable_v<T>, T> exile_launch(struct exile_policy *policy, U fn, Args && ... args)
{
size_t mapsize = sizeof(T);
T * sharedbuf = (T *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
if(sharedbuf == NULL)
{
throw std::runtime_error(std::string("mmap failed: ") + strerror(errno));
}
std::shared_ptr<void> deleter(nullptr, [sharedbuf, mapsize](...){ munmap(sharedbuf, mapsize); });
launch_arg<T, U, Args...> launcharg(policy, sharedbuf, fn, std::forward<Args>(args)...);
int (*clonefn)(void *) = &exile_clone_handle_trivial<T, U, Args...>;
/* TODO: exception or what? */
int ret = do_clone(clonefn, &launcharg);
if(ret == 0)
{
return *sharedbuf;
}
throw std::runtime_error(std::string("clone() failed: " + std::to_string(ret)));
return T();
}
template<typename T, typename U, typename ... Args>
typename std::enable_if_t<!std::is_trivially_copyable_v<T> && std::is_copy_constructible_v<T>, T>
exile_launch(struct exile_policy *policy, const std::function<size_t (const T &, char *, size_t)> &serializer, const std::function<T(const char *, size_t)> &deserializer, U fn, Args && ... args)
{
size_t mapsize = EXILE_MMAP_SIZE;
char *sharedbuf = (char *) mmap(NULL, mapsize , PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
if(sharedbuf == NULL)
{
throw std::runtime_error(std::string("mmap failed: ") + strerror(errno));
}
std::shared_ptr<void> deleter(nullptr, [sharedbuf, mapsize](...){ munmap(sharedbuf, mapsize); });
launch_arg_serializer<T, U, Args...> launcharg(policy, sharedbuf, mapsize, serializer, deserializer, fn, std::forward<Args>(args)...);
int (*clonefn)(void *) = &exile_clone_handle_serializer<T, U, Args...>;
/* TODO: exception or what? */
int ret = do_clone(clonefn, &launcharg);
if(ret == 0)
{
size_t size = 0;
memcpy(&size, sharedbuf, sizeof(size));
return deserializer(sharedbuf + sizeof(size_t), size);
}
throw std::runtime_error(std::string("clone() failed: " + std::to_string(ret)));
return T();
}
template<class T>
std::basic_string<typename T::value_type> deserialize_stdstring(const char *buf, size_t n)
{
return std::basic_string<typename T::value_type> { buf, n };
}
template<class T>
size_t serialize_stdstring(const std::basic_string<typename T::value_type> &t, char *buf, size_t n)
{
if(n < t.size())
{
return 0;
}
memcpy(buf, t.data(), t.size());
return t.size();
}
template<typename T, typename U, typename ... Args>
std::basic_string<typename T::value_type> exile_launch(struct exile_policy *policy, U fn, Args && ... args)
{
return exile_launch<T, U, Args...>(policy, &serialize_stdstring<T>, &deserialize_stdstring<T>, fn, std::forward<Args>(args) ...);
}

6
test.c
파일 보기

@ -489,6 +489,7 @@ int test_no_new_fds()
}
extern int mkpath(const char *p, mode_t mode, int baseisfile);
int test_mkpath()
{
system("rm -rf /tmp/.exile.h/");
@ -547,12 +548,14 @@ int test_fail_flags()
return 0;
}
static int *read_pipe = NULL;
int do_launch_test(void *arg)
{
int num = *(int *)(arg);
num += 1;
char buffer[512] = { 0 };
read(child_write_pipe[0], buffer, sizeof(buffer)-1);
read(*read_pipe, buffer, sizeof(buffer)-1);
printf("Sandboxed +1: %i\n", num);
printf("Echoing: %s\n", buffer);
fflush(stdout);
@ -568,6 +571,7 @@ int test_launch()
params.func = &do_launch_test;
params.funcarg = &num;
params.policy = policy;
read_pipe = &params.child_write_pipe[0];
int launchfd = exile_launch(&params, &res);
if(launchfd < 0)
{

92
test.cpp Normal file
파일 보기

@ -0,0 +1,92 @@
#include "exile.hpp"
#include "assert.h"
#include <map>
std::string sandboxed_reverse(std::string str)
{
std::reverse(str.begin(), str.end());
return str;
}
size_t stdstrlen(const std::string &str)
{
return str.size();
}
int incrementer(int arg)
{
return ++arg;
}
int test_exile_launch_trivial()
{
int u = 22;
int result = exile_launch<int>(exile_init_policy(), &incrementer, u);
assert(result == 23);
return 0;
}
int test_exile_launch_stdstring()
{
std::string str = "abc123";
std::string reversed = exile_launch<std::string>(exile_init_policy(), &sandboxed_reverse, str);
assert(reversed == "321cba");
return 0;
}
struct not_trivially_copyable
{
public:
std::string somecontent;
};
int test_exile_launch_serializer()
{
static_assert(! std::is_trivially_copyable_v<not_trivially_copyable>);
auto serializer = [](const not_trivially_copyable &obj, char *buf, size_t n){
serialize_stdstring<std::string>(obj.somecontent, buf, n);
return obj.somecontent.size();
};
auto deserializer = [](const char *buffer, size_t n) {
not_trivially_copyable obj;
obj.somecontent = deserialize_stdstring<std::string>(buffer, n);
return obj;
};
not_trivially_copyable result = exile_launch<not_trivially_copyable>(exile_init_policy(), serializer, deserializer, []() {not_trivially_copyable obj; obj.somecontent = "Just something"; return obj;});
assert(result.somecontent == "Just something");
return 0;
}
int main(int argc, char *argv[])
{
if(argc < 2)
{
std::cerr << "Missing test" << std::endl;
return 1;
}
std::map<std::string, int (*)()> map = {
{ "launch-trivial-cpp", &test_exile_launch_trivial} ,
{ "launch-stdstring-cpp", &test_exile_launch_stdstring },
{ "launch-serializer-cpp", &test_exile_launch_serializer },
};
std::string test = argv[1];
if(test == "--dumptests")
{
for(auto &entry : map)
{
std::cout << entry.first << std::endl;
}
return 0;
}
int (*fn)() = map[test];
if(fn != nullptr)
{
return fn();
}
std::cerr << "Unknown test" << std::endl;
return 1;
}

18
test.sh
파일 보기

@ -44,14 +44,15 @@ function runtest_skipped()
function runtest()
{
testname="$1"
test_log_file="$2"
testbin="$1"
testname="$2"
test_log_file="$3"
echo "Running: $testname. Date: $(date)" > "${test_log_file}"
echo -n "Running $1... "
echo -n "Running $testname... "
#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call"
(./test $1 || exit $?) &>> "${test_log_file}"
(./$testbin "$testname" || exit $?) &>> "${test_log_file}"
ret=$?
SUCCESS="no"
if [ $ret -eq 0 ] ; then
@ -64,7 +65,7 @@ function runtest()
runtest_fail
fi
echo "Finished: ${testname}. Date: $(date). Success: $SUCCESS" >> "${test_log_file}"
echo "Finished: ${testname} (${testbin}). Date: $(date). Success: $SUCCESS" >> "${test_log_file}"
}
GIT_ID=$( git log --pretty="format:%h" -n1 )
@ -79,7 +80,12 @@ LOG_OUTPUT_DIR_PATH="${LOG_OUTPUT_DIR}/exile_test_${GIT_ID}_${TIMESTAMP}"
for test in $( ./test --dumptests ) ; do
testname=$( echo $test )
runtest "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
runtest test "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
done
for test in $( ./testcpp --dumptests ) ; do
testname=$( echo $test )
runtest testcpp "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}"
done
echo
echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})"