Merge branch 'python-dev' of https://github.com/mickael-guene/PRoot into python-dev

Closes: https://github.com/proot-me/PRoot/pull/82
This commit is contained in:
Lucas Ramage 2019-02-21 20:34:19 -05:00
commit f01ace5de1
9 changed files with 498 additions and 2 deletions

View File

@ -9,7 +9,7 @@ compiler: gcc
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq libtalloc-dev libarchive-dev uthash-dev gdb strace realpath
- sudo apt-get install -qq libtalloc-dev libarchive-dev uthash-dev gdb strace realpath swig
- sudo -H pip install cpp-coveralls==0.3.12
script: make -C src loader.exe loader-m32.exe build.h && env CFLAGS=--coverage LDFLAGS='--coverage' make -C src proot care && env PATH=/bin:/usr/bin:/sbin:/usr/sbin:$PWD/src PROOT_NO_SECCOMP=1 make -C tests

View File

@ -14,6 +14,9 @@ STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
HAS_SWIG := $(shell swig -version 2>/dev/null)
HAS_PYTHON_CONFIG := $(shell python2.7-config --ldflags 2>/dev/null)
CPPFLAGS += -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I. -I$(VPATH)
CFLAGS += -Wall -Wextra -O2
LDFLAGS += -ltalloc -Wl,-z,noexecstack
@ -70,6 +73,13 @@ ifdef HAS_LOADER_32BIT
OBJECTS += loader/loader-m32-wrapped.o
endif
ifneq ($(and $(HAS_SWIG),$(HAS_PYTHON_CONFIG)),)
OBJECTS += extension/python/python.o \
extension/python/proot_wrap.o \
extension/python/python_extension.o \
extension/python/proot.o
endif
CARE_OBJECTS = \
cli/care.o \
cli/care-manual.o \
@ -108,6 +118,10 @@ CHECK_VERSION = VERSION=$$($(GIT) describe --tags --dirty --abbrev=8 --always 2>
then /bin/echo -e "\#undef VERSION\n\#define VERSION \"$${VERSION}\""; \
fi;
ifneq ($(and $(HAS_SWIG),$(HAS_PYTHON_CONFIG)),)
CHECK_PYTHON_EXTENSION = /bin/echo -e "\#define HAVE_PYTHON_EXTENSION"
endif
CHECK_FEATURES = process_vm seccomp_filter
CHECK_PROGRAMS = $(foreach feature,$(CHECK_FEATURES),.check_$(feature))
CHECK_OBJECTS = $(foreach feature,$(CHECK_FEATURES),.check_$(feature).o)
@ -130,6 +144,7 @@ build.h: $(CHECK_RESULTS)
$(Q)echo "#ifndef BUILD_H" >> $@
$(Q)echo "#define BUILD_H" >> $@
$(Q)sh -c '$(CHECK_VERSION)' >> $@
$(Q)sh -c '$(CHECK_PYTHON_EXTENSION)' >> $@
$(Q)cat $^ >> $@
$(Q)echo "#endif /* BUILD_H */" >> $@
@ -175,6 +190,37 @@ cli/care-manual.o: manual cli/cli.o
cli/%-licenses.o: licenses cli/cli.o
$(OBJIFY)
######################################################################
# Python extension
define build_python_extension
CPPFLAGS += `python2.7-config --includes`
LDFLAGS += `python2.7-config --ldflags`
SWIG = swig
quiet_SWIG = @echo " SWIG $$@"; swig
.INTERMEDIATE:python_extension.py
python_extension.py: extension/python/python_extension.py
$$(Q)cp $$< $$@
extension/python/python_extension.o: python_extension.py cli/cli.o
$$(OBJIFY)
.SECONDARY: proot_wrap.c proot.py
proot_wrap.c proot.py: extension/python/proot.i
$$($$(quiet)SWIG) -python -outcurrentdir -I$$(VPATH) $$(VPATH)/extension/python/proot.i
extension/python/proot.o: proot.py cli/cli.o
$$(OBJIFY)
extension/python/proot_wrap.o: proot_wrap.c
$$($$(quiet)CC) $$(CPPFLAGS) $$(CFLAGS) -MD -c $$< -o $$@
endef
ifneq ($(and $(HAS_SWIG),$(HAS_PYTHON_CONFIG)),)
$(eval $(build_python_extension))
endif
######################################################################
# Build rules for the loader
@ -205,6 +251,7 @@ loader$1.exe: loader/loader$1
loader/loader$1-wrapped.o: loader$1.exe cli/cli.o
$$(OBJIFY)
endef
$(eval $(build_loader))
@ -230,7 +277,7 @@ BINDIR ?= $(PREFIX)/bin
.PHONY: clean distclean install install-care uninstall
clean distclean:
-$(RM) -f $(CHECK_OBJECTS) $(CHECK_PROGRAMS) $(CHECK_RESULTS) $(OBJECTS) $(CARE_OBJECTS) $(LOADER_OBJECTS) $(LOADER-m32_OBJECTS) proot care loader/loader loader/loader-m32 cli/care-manual.o $(DEPS) build.h licenses
-$(RM) -f $(CHECK_OBJECTS) $(CHECK_PROGRAMS) $(CHECK_RESULTS) $(OBJECTS) $(CARE_OBJECTS) $(LOADER_OBJECTS) $(LOADER-m32_OBJECTS) proot care loader/loader loader/loader-m32 cli/care-manual.o $(DEPS) build.h licenses proot.py proot_wrap.c
install: proot
$($(quiet)INSTALL) -D $< $(DESTDIR)$(BINDIR)/$<

View File

@ -323,6 +323,14 @@ static int handle_option_n(Tracee *tracee, const Cli *cli UNUSED, const char *va
return status;
}
#ifdef HAVE_PYTHON_EXTENSION
static int handle_option_P(Tracee *tracee, const Cli *cli UNUSED, const char *value)
{
(void) initialize_extension(tracee, python_callback, value);
return 0;
}
#endif
/**
* Initialize @tracee->qemu.
*/

View File

@ -60,6 +60,9 @@ static int handle_option_0(Tracee *tracee, const Cli *cli, const char *value);
static int handle_option_i(Tracee *tracee, const Cli *cli, const char *value);
static int handle_option_p(Tracee *tracee, const Cli *cli, const char *value);
static int handle_option_n(Tracee *tracee, const Cli *cli, const char *value);
#ifdef HAVE_PYTHON_EXTENSION
static int handle_option_P(Tracee *tracee, const Cli *cli, const char *value);
#endif
static int handle_option_R(Tracee *tracee, const Cli *cli, const char *value);
static int handle_option_S(Tracee *tracee, const Cli *cli, const char *value);
static int handle_option_kill_on_exit(Tracee *tracee, const Cli *cli, const char *value);
@ -260,6 +263,16 @@ Copyright (C) 2015 STMicroelectronics, licensed under GPL v2 or later.",
\tto run multiple instances of a same program without worrying about the same ports\n\
\tbeing used twice.",
},
#ifdef HAVE_PYTHON_EXTENSION
{ .class = "Extension options",
.arguments = {
{ .name = "-P", .separator = ' ', .value = "string" },
{ .name = NULL, .separator = '\0', .value = NULL } },
.handler = handle_option_p,
.description = "Allow to access tracee information from python (experimental).",
.detail = "\tThis option allow to launch a python script as an extension (experimental).",
},
#endif
{ .class = "Alias options",
.arguments = {
{ .name = "-R", .separator = ' ', .value = "path" },

View File

@ -180,6 +180,7 @@ static inline int notify_extensions(Tracee *tracee, ExtensionEvent event,
extern int kompat_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2);
extern int fake_id0_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2);
extern int care_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2);
extern int python_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2);
/* Added extensions. */
/**

View File

@ -0,0 +1,104 @@
%module proot
%{
#include "arch.h"
#include "syscall/sysnum.h"
#include "tracee/tracee.h"
#include "tracee/reg.h"
#include "tracee/mem.h"
#include "extension/extension.h"
/* define an internal global with correct PR number */
#define SYSNUM(item) static const int PR_internal ## item = PR_ ## item;
#include "syscall/sysnums.list"
#undef SYSNUM
%}
/* now say PR_item has value PR_internal */
/* works but ugly. Another way to do this ? */
#define SYSNUM(item) static const int PR_ ## item = PR_internal ## item;
%include "syscall/sysnums.list"
#undef SYSNUM
/* python extension helper */
%inline %{
Tracee *get_tracee_from_extension(long extension_handle)
{
Extension *extension = (Extension *)extension_handle;
Tracee *tracee = TRACEE(extension);
return tracee;
}
%}
/* arch.h */
typedef unsigned long word_t;
/* tracee/tracee.h */
typedef enum {
CURRENT = 0,
ORIGINAL = 1,
MODIFIED = 2,
NB_REG_VERSION
} RegVersion;
/* syscall/sysnum.h */
typedef enum Sysnum;
extern Sysnum get_sysnum(const Tracee *tracee, RegVersion version);
extern void set_sysnum(Tracee *tracee, Sysnum sysnum);
/* tracee/reg.h */
typedef enum {
SYSARG_NUM = 0,
SYSARG_1,
SYSARG_2,
SYSARG_3,
SYSARG_4,
SYSARG_5,
SYSARG_6,
SYSARG_RESULT,
STACK_POINTER,
INSTR_POINTER,
RTLD_FINI,
STATE_FLAGS,
USERARG_1,
} Reg;
extern word_t peek_reg(const Tracee *tracee, RegVersion version, Reg reg);
extern void poke_reg(Tracee *tracee, Reg reg, word_t value);
/* tracee/mem.h */
/* make read_data / write_data pythonic */
%apply (char *STRING, size_t LENGTH) { (const void *src_tracer, word_t size2) };
extern int write_data(const Tracee *tracee, word_t dest_tracee, const void *src_tracer, word_t size2);
%include <cstring.i>
%rename(read_data) read_data_for_python;
%cstring_output_withsize(void *dest_tracer, int *size2);
%inline %{
void read_data_for_python(const Tracee *tracee, word_t src_tracee, void *dest_tracer, int *size2)
{
int res = read_data(tracee, dest_tracer, src_tracee, *size2);
/* in case of error we return empty string */
if (res)
*size2 = 0;
}
%}
/* extension/extention.h */
typedef enum {
GUEST_PATH,
HOST_PATH,
SYSCALL_ENTER_START,
SYSCALL_ENTER_END,
SYSCALL_EXIT_START,
SYSCALL_EXIT_END,
NEW_STATUS,
INHERIT_PARENT,
INHERIT_CHILD,
SYSCALL_CHAINED_ENTER,
SYSCALL_CHAINED_EXIT,
INITIALIZATION,
REMOVED,
PRINT_CONFIG,
PRINT_USAGE,
} ExtensionEvent;

View File

@ -0,0 +1,185 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <Python.h>
#include "extension/extension.h"
#include "cli/note.h"
#include "path/temp.h"
/* FIXME: need to handle error code properly */
static PyObject *python_callback_func;
//static bool is_seccomp_disabling_done = false;
/* List of syscalls handled by this extensions. */
static FilteredSysnum filtered_sysnums[] = {
FILTERED_SYSNUM_END,
};
/* build by swig */
extern void init_proot(void);
/* create python files */
extern unsigned char _binary_python_extension_py_start;
extern unsigned char _binary_python_extension_py_size;
extern unsigned char _binary_proot_py_start;
extern unsigned char _binary_proot_py_size;
static int create_python_file(const char *tmp_dir, const char *python_file_name, unsigned char *start_file, unsigned char *size_file)
{
void *start = (void *) start_file;
size_t size = (size_t) size_file;
char python_full_file_name[PATH_MAX];
int fd;
int status;
status = snprintf(python_full_file_name, PATH_MAX, "%s/%s", tmp_dir, python_file_name);
if (status < 0 || status >= PATH_MAX) {
status = -1;
} else {
fd = open(python_full_file_name, O_WRONLY | O_CREAT, S_IRWXU);
if (fd >= 0) {
status = write(fd, start, size);
close(fd);
}
}
return status>0?0:-1;
}
static int create_python_extension(const char *tmp_dir)
{
return create_python_file(tmp_dir, "python_extension.py",
&_binary_python_extension_py_start,
&_binary_python_extension_py_size);
}
static int create_proot(const char *tmp_dir)
{
return create_python_file(tmp_dir, "proot.py",
&_binary_proot_py_start,
&_binary_proot_py_size);
}
/* init python once */
void init_python_env()
{
static bool is_done = false;
if (!is_done) {
char path_insert[PATH_MAX];
PyObject *pName, *pModule;
const char *tmp_dir;
int status;
tmp_dir = create_temp_directory(NULL, "proot-python");
status = snprintf(path_insert, PATH_MAX, "sys.path.insert(0, '%s')", tmp_dir);
if (status < 0 || status >= PATH_MAX) {
note(NULL, ERROR, USER, "Unable to create tmp directory\n");
} else if (create_python_extension(tmp_dir) || create_proot(tmp_dir)) {
note(NULL, ERROR, USER, "Unable to create python file\n");
is_done = true;
} else {
Py_Initialize();
init_proot();
PyRun_SimpleString("import sys");
PyRun_SimpleString(path_insert);
pName = PyString_FromString("python_extension");
if (pName) {
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule) {
python_callback_func = PyObject_GetAttrString(pModule, "python_callback");
if (python_callback_func && PyCallable_Check(python_callback_func))
;//note(NULL, INFO, USER, "python_callback find\n");
else
note(NULL, ERROR, USER, "python_callback_func error\n");
} else {
PyErr_Print();
note(NULL, ERROR, USER, "pModule error\n");
}
} else
note(NULL, ERROR, USER, "pName error\n");
is_done = true;
}
}
}
/* call python callback */
static int python_callback_func_wrapper(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2)
{
int res = 0;
PyObject *pArgs;
PyObject *pValue;
pArgs = PyTuple_New(4);
if (pArgs) {
/* setargs */
pValue = PyLong_FromLong((long) extension);
if (!pValue)
note(NULL, ERROR, USER, "pValue allocation failure\n");
PyTuple_SetItem(pArgs, 0, pValue);
pValue = PyInt_FromLong(event);
if (!pValue)
note(NULL, ERROR, USER, "pValue allocation failure\n");
PyTuple_SetItem(pArgs, 1, pValue);
pValue = PyInt_FromLong(data1);
if (!pValue)
note(NULL, ERROR, USER, "pValue allocation failure\n");
PyTuple_SetItem(pArgs, 2, pValue);
pValue = PyInt_FromLong(data2);
if (!pValue)
note(NULL, ERROR, USER, "pValue allocation failure\n");
PyTuple_SetItem(pArgs, 3, pValue);
/* call function */
pValue = PyObject_CallObject(python_callback_func, pArgs);
if (pValue != NULL) {
res = PyInt_AsLong(pValue);
Py_DECREF(pValue);
} else {
PyErr_Print();
note(NULL, ERROR, USER, "fail to call callback\n");
}
Py_DECREF(pArgs);
} else
note(NULL, ERROR, USER, "pArgs allocation failure\n");
return res;
}
/**
* Handler for this @extension. It is triggered each time an @event
* occurred. See ExtensionEvent for the meaning of @data1 and @data2.
*/
int python_callback(Extension *extension, ExtensionEvent event, intptr_t data1, intptr_t data2)
{
int res = 0;
switch (event) {
case INITIALIZATION:
{
/* not working. Use 'export PROOT_NO_SECCOMP=1' */
/*if (!is_seccomp_disabling_done) {
Tracee *tracee = TRACEE(extension);
if (tracee->seccomp == ENABLED)
tracee->seccomp = DISABLING;
is_seccomp_disabling_done = true;
}*/
init_python_env();
res = python_callback_func_wrapper(extension, event, data1, data2);
extension->filtered_sysnums = filtered_sysnums;
}
break;
default:
res = python_callback_func_wrapper(extension, event, data1, data2);
}
return res;
}

View File

@ -0,0 +1,19 @@
from proot import *
import ctypes
import imp
client = None
def python_callback(extension, event, data1, data2):
global client
res = 0
if event == 11:
if client:
print "Already have a client => refuse to use %s" % (ctypes.string_at(data1))
else:
client = imp.load_source('client', ctypes.string_at(data1))
if client:
return client.python_callback(extension, event, data1, data2)
return 0

119
tests/test-python01.sh Normal file
View File

@ -0,0 +1,119 @@
#!/bin/sh
# Test for Python Extension for PRoot
set -eu
# Check for test dependencies
for cmd in mcookie cat grep rm find; do
if ! command -v "${cmd}" > /dev/null; then
exit 125
fi
done
# Check for PRoot binary
if [ ! -e "${PROOT}" ]; then
exit 125
fi
# Check for python flag
if ! "${PROOT}" --help | grep -- -p | grep string; then
exit 125
fi
TMP="$(mcookie)_hide.py"
# The following python script will hide all files
cat > "/tmp/${TMP}" <<EOF
import struct
from proot import *
class Dents(object):
"""docstring for Dents"""
def __init__(self):
super(Dents, self).__init__()
self.d_ino = 0
self.d_off = 0
self.d_reclen = 0
self.d_name = None
self.d_type = 0
def get_real_size(self, buf):
res = 0
for b in buf:
if b == 0:
return res
res += 1
return res
def unpack(self, buf):
dirent_header_format = "LLH"
(self.d_ino, self.d_off, self.d_reclen) = struct.unpack_from(dirent_header_format, buf)
max_size = self.d_reclen - 2 - struct.calcsize(dirent_header_format)
d_name = struct.unpack_from('%dB' % max_size, buf, struct.calcsize(dirent_header_format))
real_size = self.get_real_size(d_name)
(self.d_name, ) = struct.unpack_from('%ds' % real_size, buf, struct.calcsize(dirent_header_format))
(self.d_type, ) = struct.unpack_from('B', buf, self.d_reclen - 1)
return self.d_reclen
def pack(self):
dirent_header_size = struct.calcsize("LLH")
d_name_length = len(self.d_name)
d_reclen = d_name_length + 2 + dirent_header_size
d_reclen = (d_reclen + 7) & ~7
d_name_length = d_reclen - 2 - dirent_header_size
buf = struct.pack('LLH%dsBB' % d_name_length, self.d_ino, self.d_off, self.d_reclen, self.d_name, 0, self.d_type)
return buf
def get_getdents(tracee, res, addr):
dents = []
buf = read_data(tracee, addr, res)
pos = 0
while pos < res:
d = Dents()
pos += d.unpack(buf[pos:])
dents.append(d)
return dents
def put_dents(tracee, addr, dents):
pos = 0
for d in dents:
buf = d.pack()
write_data(tracee, pos + addr, buf)
pos += len(buf)
poke_reg(tracee, SYSARG_RESULT, pos)
def filter_dents(dents):
res = []
for d in dents:
#we remove all files
if d.d_type != 8:
res.append(d)
return res
def python_callback(extension, event, data1, data2):
if event == SYSCALL_EXIT_END:
tracee = get_tracee_from_extension(extension)
no = get_sysnum(tracee, CURRENT)
if no == PR_getdents:
res = peek_reg(tracee, CURRENT, SYSARG_RESULT)
if res > 0:
addr = peek_reg(tracee, CURRENT, SYSARG_2)
dents = get_getdents(tracee, res, addr)
dents = filter_dents(dents)
put_dents(tracee, addr, dents)
return 0
EOF
# If script passes result from find will be empty
if [ "$(env PROOT_NO_SECCOMP=1 "${PROOT}" -p "/tmp/${TMP}" find . -maxdepth 1 -type f)" ]; then
exit 1
fi
rm -rf "/tmp/${TMP}" "/tmp/${TMP}c"