[PATCH v10 3/3] tests: check LuaJIT scripting support

Victor Krapivensky krapivenskiy.va at phystech.edu
Wed Aug 2 09:50:58 UTC 2017


* tests/.gitignore: Add lua.
* tests/Makefile.am (check_PROGRAMS): Likewise.
(LUAJIT_TESTS): New variable.
(TESTS): Add LUAJIT_TESTS.
(EXTRA_DIST): Add lua.sh, lua-basics.test, lua-qual.test,
lua-tampering.test.
* tests/lua-basics.test: New file.
* tests/lua-qual.test: Likewise.
* tests/lua-tampering.test: Likewise.
* tests/lua.c: Likewise.
* tests/lua.sh: Likewise.
---
 tests/.gitignore         |   1 +
 tests/Makefile.am        |  13 ++-
 tests/lua-basics.test    | 250 +++++++++++++++++++++++++++++++++++++++++++++++
 tests/lua-qual.test      |  58 +++++++++++
 tests/lua-tampering.test | 149 ++++++++++++++++++++++++++++
 tests/lua.c              | 115 ++++++++++++++++++++++
 tests/lua.sh             |  11 +++
 7 files changed, 596 insertions(+), 1 deletion(-)
 create mode 100755 tests/lua-basics.test
 create mode 100755 tests/lua-qual.test
 create mode 100755 tests/lua-tampering.test
 create mode 100644 tests/lua.c
 create mode 100644 tests/lua.sh

diff --git a/tests/.gitignore b/tests/.gitignore
index 47ecc6ca..9abab8a0 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -161,6 +161,7 @@ lookup_dcookie
 lseek
 lstat
 lstat64
+lua
 madvise
 mbind
 membarrier
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 5687a8d8..afcdd2c7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -98,6 +98,7 @@ check_PROGRAMS = $(PURE_EXECUTABLES) \
 	ioctl_nsfs \
 	ioctl_rtc-v \
 	ksysent \
+	lua \
 	mmsg-silent \
 	mmsg_name-v \
 	msg_control-v \
@@ -177,6 +178,12 @@ else
 LIBUNWIND_TESTS =
 endif
 
+if USE_LUAJIT
+LUAJIT_TESTS = lua-basics.test lua-qual.test lua-tampering.test
+else
+LUAJIT_TESTS =
+endif
+
 DECODER_TESTS = \
 	brk.test \
 	btrfs-v.test \
@@ -279,7 +286,7 @@ MISC_TESTS = \
 	threads-execve.test \
 	# end of MISC_TESTS
 
-TESTS = $(GEN_TESTS) $(DECODER_TESTS) $(MISC_TESTS) $(LIBUNWIND_TESTS)
+TESTS = $(GEN_TESTS) $(DECODER_TESTS) $(MISC_TESTS) $(LIBUNWIND_TESTS) $(LUAJIT_TESTS)
 
 XFAIL_TESTS_ =
 XFAIL_TESTS_m32 = $(LIBUNWIND_TESTS)
@@ -316,6 +323,10 @@ EXTRA_DIST = \
 	ipc_msgbuf.expected \
 	ksysent.sed \
 	lstatx.c \
+	lua.sh \
+	lua-basics.test \
+	lua-qual.test \
+	lua-tampering.test \
 	match.awk \
 	net.expected \
 	netlink_sock_diag-v.sh \
diff --git a/tests/lua-basics.test b/tests/lua-basics.test
new file mode 100755
index 00000000..bac28422
--- /dev/null
+++ b/tests/lua-basics.test
@@ -0,0 +1,250 @@
+#!/bin/sh
+
+. "${srcdir=.}/lua.sh"
+
+run_with_script()
+{
+	run_strace_with_script -e trace=readv,writev ../lua "$@" > "$EXP"
+	match_diff "$LOG" "$EXP"
+}
+
+DATA=0123abcdefghijklnmop
+
+run_with_script $DATA $DATA <<EOF
+EOF
+
+run_with_script $DATA $DATA <<EOF
+ntotal = 10
+ncur = 0
+for i = 0, ntotal - 1 do
+	strace.at_exit(function()
+		assert(ncur == i)
+		ncur = ncur + 1
+		if ncur == ntotal then
+			assert(io.open('at-exit-marker', 'w'))
+		end
+	end)
+end
+EOF
+if ! [ -f at-exit-marker ]; then
+	fail_ "'at-exit-marker' does not exist"
+fi
+
+run_with_script $DATA $DATA <<EOF
+for i = 1, 10 do assert(strace.C.next_sc() == nil) end
+EOF
+
+when_decls()
+{
+	echo "----- start of 'when_decls $*' -----"
+	case "$1" in
+	entering)
+		echo "\
+hooks_per_syscall = 1
+function make_state_checker()
+	return function(tcp)
+		assert(tcp ~= nil)
+		assert(strace.entering(tcp))
+		assert(not strace.exiting(tcp))
+	end
+end"
+		case "$2" in
+			s) echo "when_obj = 'entering'" ;;
+			t) echo "when_obj = {true, false}" ;;
+		esac
+	;;
+	exiting)
+		echo "\
+hooks_per_syscall = 1
+function make_state_checker()
+	return function(tcp)
+		assert(tcp ~= nil)
+		assert(strace.exiting(tcp))
+		assert(not strace.entering(tcp))
+	end
+end"
+		case "$2" in
+			s) echo "when_obj = 'exiting'" ;;
+			t) echo "when_obj = {false, true}" ;;
+		esac
+	;;
+	both)
+		echo "\
+hooks_per_syscall = 2
+function make_state_checker()
+	local expect_entry = true
+	return function(tcp)
+		assert(tcp ~= nil)
+		if expect_entry then
+			assert(strace.entering(tcp))
+			assert(not strace.exiting(tcp))
+		else
+			assert(strace.exiting(tcp))
+			assert(not strace.entering(tcp))
+		end
+		expect_entry = not expect_entry
+	end
+end"
+		case "$2" in
+			s) echo "when_obj = 'both'" ;;
+			t) echo "when_obj = {true, true}" ;;
+		esac
+	;;
+	esac
+	echo "----- end of 'when_decls $*' -----"
+}
+
+make_hook_decl="\
+----- start of 'make_hook_decl' -----
+function make_hook(no_at_exit_hook, state_checker)
+	state_checker = state_checker or make_state_checker()
+	local nwritev, nreadv = 0, 0
+	local function check_nreadv()
+		assert(nreadv == hooks_per_syscall)
+	end
+	local function hook(tcp, kind)
+		state_checker(tcp)
+		local name = strace.get_sc_name(tcp.scno, tcp)
+		if kind == 'writev' or kind == 'readv' then
+			assert(name == kind)
+		elseif kind == '|' then
+			assert(name == 'writev' or name == 'readv')
+		else
+			assert(kind == '*', 'unknown \"kind\" value')
+		end
+		if name == 'writev' then
+			assert(nreadv == 0)
+			nwritev = nwritev + 1
+		elseif name == 'readv' then
+			assert(nwritev == hooks_per_syscall)
+			nreadv = nreadv + 1
+		end
+	end
+	if no_at_exit_hook then
+		return hook, check_nreadv
+	else
+		strace.at_exit(check_nreadv)
+		return hook
+	end
+end
+----- end of 'make_hook_decl' -----"
+
+for when in entering exiting both; do
+	run_with_script $DATA $DATA <<EOF
+$(when_decls $when t)
+$make_hook_decl
+strace.C.monitor_all(when_obj[1], when_obj[2])
+check_state = make_state_checker()
+tcp = strace.C.next_sc()
+check_state(tcp)
+assert(strace.get_sc_name(tcp.scno, tcp)	  == 'execve')
+assert(strace.get_sc_name(tcp.scno, tcp.currpers) == 'execve')
+if strace.entering(tcp) then
+	assert(strace.read_path(tcp.u_arg[0]) == '../lua')
+	assert(strace.path_match '../lua')
+	assert(strace.path_match{'aaa', '../lua', 'bbb'})
+	assert(not strace.path_match{'aaa', 'bbb'})
+	assert(not strace.path_match 'aaa')
+end
+hook, final_check = make_hook(true, check_state)
+while strace.C.next_sc() ~= nil do
+	hook(tcp, '*')
+end
+final_check()
+for i = 1, 10 do assert(strace.C.next_sc() == nil) end
+EOF
+
+	run_with_script $DATA $DATA <<EOF
+$(when_decls $when t)
+check_state = make_state_checker()
+for _, scname in ipairs{'writev', 'readv'} do
+	for p = 0, strace.npersonalities - 1 do
+		local scno = strace.get_scno(scname, p)
+		if scno then
+			assert(not not strace.C.monitor(scno, p, when_obj[1],
+				when_obj[2]))
+		end
+	end
+	for i = 1, hooks_per_syscall do
+		tcp = strace.C.next_sc()
+		check_state(tcp)
+		assert(strace.get_sc_name(tcp.scno, tcp) == scname)
+	end
+end
+for i = 1, 10 do assert(strace.C.next_sc() == nil) end
+EOF
+
+	for objtype in s t; do
+		for n in 1 5; do
+			run_with_script $DATA $DATA <<EOF
+$(when_decls $when $objtype)
+$make_hook_decl
+for i = 1, $n do
+	local hook = make_hook()
+	strace.hook('writev', when_obj, function(tcp) hook(tcp, 'writev') end)
+	strace.hook('readv', when_obj, function(tcp) hook(tcp, 'readv') end)
+end
+EOF
+		done
+
+		run_with_script $DATA $DATA <<EOF
+$(when_decls $when $objtype)
+$make_hook_decl
+hook = make_hook()
+strace.hook({'readv', 'writev'}, when_obj, function(tcp) hook(tcp, '|') end)
+EOF
+
+		run_with_script $DATA $DATA <<EOF
+$(when_decls $when $objtype)
+$make_hook_decl
+hook = make_hook()
+for p = 0, strace.npersonalities - 1 do
+	local t = {}
+	t[#t + 1] = strace.get_scno('writev', p)
+	t[#t + 1] = strace.get_scno('readv', p)
+	strace.hook_scno(t, when_obj, function(tcp) hook(tcp, '|') end, p)
+end
+EOF
+
+		run_with_script $DATA $DATA <<EOF
+$(when_decls $when $objtype)
+$make_hook_decl
+hook = make_hook()
+strace.hook_class({'%desc', '%network'}, when_obj,
+	function(tcp) hook(tcp, '*') end)
+EOF
+
+	done
+done
+
+run_with_script $DATA $DATA <<EOF
+assert(strace.get_sc_name(-1, 0) == nil)
+assert(strace.get_sc_name(strace.C.nsysent_vec[0], 0) == nil)
+
+assert(strace.get_scno('', 0) == nil)
+assert(strace.get_scno('some nonsense', 0) == nil)
+
+assert(strace.get_err_name(-1, 0) == nil)
+assert(strace.get_err_name(strace.C.nerrnoent_vec[0], 0) == nil)
+
+assert(strace.get_errno('', 0) == nil)
+assert(strace.get_errno('some nonsense', 0) == nil)
+assert(strace.get_errno('EPERM', 0) == 1)
+
+assert(strace.get_signo('', 0) == nil)
+assert(strace.get_signo('some nonsense', 0) == nil)
+assert(strace.get_signo('SIGKILL', 0) == 9)
+
+ffi = require 'ffi'
+function check_ioctl_for(index, pers)
+	local entry = strace.C.ioctlent_vec[pers][index]
+	assert(strace.get_ioctl_name(entry.code, pers)
+		== ffi.string(entry.symbol))
+end
+check_ioctl_for(0, 0)
+n = strace.C.nioctlent_vec[0]
+check_ioctl_for(n / 2, 0)
+check_ioctl_for(n - 1, 0)
+assert(strace.get_ioctl_name(
+	strace.C.ioctlent_vec[0][n - 1].code + 1, 0) == nil)
+EOF
diff --git a/tests/lua-qual.test b/tests/lua-qual.test
new file mode 100755
index 00000000..19c91bd6
--- /dev/null
+++ b/tests/lua-qual.test
@@ -0,0 +1,58 @@
+#!/bin/sh
+set -e
+
+. "${srcdir=.}/lua.sh"
+
+run_prog ../umovestr
+pattern_abbrev_verbose='execve("\.\./umovestr", \["\.\./umovestr"\], 0x[[:xdigit:]]* /\* [[:digit:]]* vars \*/) = 0'
+pattern_nonabbrev_verbose='execve("\.\./umovestr", \["\.\./umovestr"\], \[".*\"\(\.\.\.\)\?\]) = 0'
+pattern_nonverbose='execve("\.\./umovestr", 0x[[:xdigit:]]*, 0x[[:xdigit:]]*) = 0'
+pattern_raw='execve(0x[[:xdigit:]]*, 0x[[:xdigit:]]*, 0x[[:xdigit:]]*) = 0'
+
+check_output_mismatch()
+{
+	local pattern
+	pattern="$1"; shift
+	run_strace_with_script "$@" ../umovestr
+	LC_ALL=C grep -x "$pattern" "$LOG" > /dev/null || {
+		printf '%s\n%s\n' \
+			'Failed patterns of expected output:' "$pattern"
+		dump_log_and_fail_with "output mismatch"
+	}
+}
+
+gen_script()
+{
+	local not=
+	if [ "$1" = '!' ]; then
+		not=not
+		shift
+	fi
+	echo "\
+strace.C.monitor_all(true, false)
+while true do
+	tcp = strace.C.next_sc()
+	if tcp == nil then
+		break
+	end
+	strace.$1(tcp, $not strace.get_sc_name(tcp.scno, tcp) == 'execve')
+end"
+}
+
+gen_script trace | check_output_mismatch "$pattern_abbrev_verbose"
+
+LC_ALL=C grep -v -x "$pattern_abbrev_verbose" "$LOG" |
+LC_ALL=C grep '^[[:alnum:]_]*(' > /dev/null &&
+	dump_log_and_fail_with "unexpected output"
+
+gen_script \! abbrev | check_output_mismatch "$pattern_nonabbrev_verbose"
+gen_script \! verbose | check_output_mismatch "$pattern_nonverbose"
+gen_script raw | check_output_mismatch "$pattern_raw"
+
+check_output_mismatch "$pattern_abbrev_verbose" -e trace=none <<EOF
+strace.hook_class('%process', 'entering', strace.trace)
+EOF
+LC_ALL=C grep '^chdir' "$LOG" > /dev/null &&
+	dump_log_and_fail_with "unexpected output"
+
+exit 0
diff --git a/tests/lua-tampering.test b/tests/lua-tampering.test
new file mode 100755
index 00000000..b78572ba
--- /dev/null
+++ b/tests/lua-tampering.test
@@ -0,0 +1,149 @@
+#!/bin/sh
+
+. "${srcdir=.}/lua.sh"
+
+run_with_script()
+{
+	run_strace_with_script -e trace=readv,writev ../lua "$@" > "$EXP"
+	match_diff "$LOG" "$EXP"
+}
+
+DATA=0123abcdefghijklnmop
+
+check_prog awk
+
+iovec_decls="\
+----- start of 'iovec_decls' -----
+ffi.cdef[[
+struct iovec {
+	void *iov_base;
+	size_t iov_len;
+};
+]]
+function decode_iovec(addr, pers)
+	local kulong = ffi.typeof('kernel_ulong_t')
+	if strace.C.pers_wordsize[pers] < ffi.sizeof(kulong) then
+		local v = strace.read_obj(addr, 'unsigned int [2]')
+		return kulong(v[0]), kulong(v[1])
+	else
+		local v = assert(strace.read_obj(addr, 'struct iovec'))
+		return strace.ptr_to_kulong(v.iov_base), kulong(v.iov_len)
+	end
+end
+----- end of 'iovec_decls' -----"
+
+run_with_script $DATA $DATA <<EOF
+ffi = require 'ffi'
+$iovec_decls
+function check_vecs(tcp, realsz)
+	assert(tcp.u_arg[2] == 1)
+	local v_base, v_len = decode_iovec(tcp.u_arg[1], tcp.currpers)
+	local n = realsz or v_len
+	assert(n == ${#DATA})
+	local buf = assert(strace.read_obj(v_base, 'char [?]', n))
+	assert(ffi.string(buf, n) == '$DATA')
+end
+strace.hook('writev', 'entering', check_vecs)
+strace.hook('readv', 'exiting', function(tcp)
+	assert(tcp.u_rval ~= -1)
+	check_vecs(tcp, tcp.u_rval)
+end)
+EOF
+
+for s in '' A AB ABC ABCD ABCDEFGH ABCDEFGHIJK; do
+	run_with_script $DATA "$s" <<EOF
+ffi = require 'ffi'
+$iovec_decls
+s = '$s'
+strace.hook('readv', 'entering', function(tcp)
+	assert(tcp.u_arg[2] == 1)
+	local v_base, v_len = decode_iovec(tcp.u_arg[1], tcp.currpers)
+	assert(v_len >= #s)
+	assert(strace.C.upoke(v_base, #s, s) == 0)
+	assert(not not strace.C.inject_retval(#s))
+end)
+EOF
+done
+
+for off in 1 2 3; do
+	for str in A ABC ABCD ABCDEFGH; do
+		expected=$(echo | awk -v d="$DATA" -v s="$str" -v off="$off" \
+			"{print substr(d,1,off) s substr(d,1+off+length(s))}")
+		run_with_script $DATA $expected <<EOF
+ffi = require 'ffi'
+$iovec_decls
+ins_str, ins_off = '$str', $off
+strace.hook('readv', 'exiting', function(tcp)
+	assert(tcp.u_arg[2] == 1)
+	local v_base, v_len = decode_iovec(tcp.u_arg[1], tcp.currpers)
+	assert(v_len >= #ins_str + ins_off)
+	assert(strace.write_obj(v_base + ins_off,
+		ffi.new('char [?]', #ins_str, ins_str)))
+end)
+EOF
+	done
+done
+
+run_with_script -EPIPE $DATA $DATA <<EOF
+first = true
+strace.hook('writev', 'entering', function(tcp)
+	if first then
+		strace.inject_error(tcp, 'EPIPE')
+		first = false
+	end
+end)
+EOF
+
+run_with_script -SIGUSR1 $DATA $DATA <<EOF
+first = true
+strace.hook('writev', 'entering', function(tcp)
+	if first then
+		strace.inject_signal(tcp, 'SIGUSR1')
+		first = false
+	end
+end)
+EOF
+
+run_with_script -EPIPE -SIGUSR1 $DATA $DATA <<EOF
+first = true
+strace.hook('writev', 'entering', function(tcp)
+	if first then
+		strace.inject_signal(tcp, 'SIGUSR1')
+		strace.inject_error(tcp, 'EPIPE')
+		first = false
+	end
+end)
+EOF
+
+run_with_script -EPIPE -SIGUSR1 $DATA $DATA <<EOF
+first = true
+strace.hook('writev', 'entering', function(tcp)
+	if first then
+		strace.inject_error(tcp, 'EPIPE')
+		strace.inject_signal(tcp, 'SIGUSR1')
+		first = false
+	end
+end)
+EOF
+
+expected=tESt
+run_with_script -EPIPE -SIGUSR1 $DATA $expected <<EOF
+first = true
+strace.hook('writev', 'entering', function(tcp)
+	if first then
+		strace.inject_error(tcp, 'EPIPE')
+		strace.inject_signal(tcp, 'SIGUSR1')
+		first = false
+	end
+end)
+ffi = require 'ffi'
+$iovec_decls
+s = '$expected'
+strace.hook('readv', 'entering', function(tcp)
+	assert(not not strace.C.inject_retval(#s))
+	assert(tcp.u_arg[2] == 1)
+	local v_base, v_len = decode_iovec(tcp.u_arg[1], tcp.currpers)
+	assert(v_len >= #s)
+	assert(strace.C.upoke(v_base, #s, s) == 0)
+end)
+EOF
diff --git a/tests/lua.c b/tests/lua.c
new file mode 100644
index 00000000..96102749
--- /dev/null
+++ b/tests/lua.c
@@ -0,0 +1,115 @@
+#include "tests.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <signal.h>
+#include <errno.h>
+
+static volatile int got_sig = 0;
+
+static void
+handler(int sig)
+{
+	got_sig = 1;
+}
+
+static void
+expect_sigusr1_once(void)
+{
+	static bool first = true;
+	if (first) {
+		assert(got_sig);
+		got_sig = 0;
+		tprintf("--- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_KERNEL} "
+			"---\n");
+		first = false;
+	} else
+		assert(!got_sig);
+}
+
+int
+main(int argc, char **argv)
+{
+	tprintf("%s", "");
+
+	const struct sigaction act = { .sa_handler = handler };
+	if (sigaction(SIGUSR1, &act, NULL))
+		perror_msg_and_fail("sigaction");
+
+	sigset_t mask;
+	sigemptyset(&mask);
+	sigaddset(&mask, SIGUSR1);
+	if (sigprocmask(SIG_UNBLOCK, &mask, NULL))
+		perror_msg_and_fail("sigprocmask");
+
+	bool expect_sigusr1 = false;
+	bool expect_epipe = false;
+
+	int curarg;
+	for (curarg = 1; curarg < argc; ++curarg) {
+		if (strcmp(argv[curarg], "-EPIPE") == 0)
+			expect_epipe = true;
+		else if (strcmp(argv[curarg], "-SIGUSR1") == 0)
+			expect_sigusr1 = true;
+		else
+			break;
+	}
+	assert(argc - curarg == 2);
+	char *towrite = argv[curarg++];
+	size_t ntowrite = strlen(towrite);
+	char *toread = argv[curarg++];
+	size_t ntoread = strlen(toread);
+
+	int fds[2];
+	if (pipe(fds) < 0)
+		perror_msg_and_fail("pipe");
+
+	if (expect_epipe) {
+		assert(writev(fds[1], (const struct iovec [1]) {{
+				.iov_base = towrite,
+				.iov_len = ntowrite,
+			}}, 1) == -1 && errno == EPIPE);
+		tprintf("writev(%d, [{iov_base=\"%s\", iov_len=%zu}], 1) = "
+			"-1 EPIPE (%s) (INJECTED)\n",
+			fds[1], towrite, ntowrite, strerror(EPIPE));
+		if (expect_sigusr1)
+			expect_sigusr1_once();
+	}
+
+	assert(writev(fds[1], (const struct iovec [1]) {{
+				.iov_base = towrite,
+				.iov_len = ntowrite,
+			}}, 1) == (ssize_t) ntowrite);
+	tprintf("writev(%d, [{iov_base=\"%s\", iov_len=%zu}], 1) = %zu\n",
+		fds[1], towrite, ntowrite, ntowrite);
+	if (expect_sigusr1)
+		expect_sigusr1_once();
+
+	if (close(fds[1]) < 0)
+		perror_msg_and_fail("close");
+
+	char *buf = malloc(ntoread + 1);
+	if (!buf)
+		perror_msg_and_fail("malloc");
+
+	assert(readv(fds[0], (const struct iovec [1]) {{
+			.iov_base = buf,
+			.iov_len = ntoread + 1,
+		}}, 1) == (ssize_t) ntoread);
+	if (ntoread && memcmp(buf, toread, ntoread) != 0) {
+		buf[ntoread] = '\0';
+		error_msg_and_fail("expected to read '%s', got '%s'",
+			toread, buf);
+		return 1;
+	}
+	tprintf("readv(%d, [{iov_base=\"%s\", iov_len=%zu}], 1) = %zu%s\n",
+		fds[0], toread, ntoread + 1, ntoread,
+		ntoread == ntowrite ? "" : " (INJECTED)");
+
+	tprintf("+++ exited with 0 +++\n");
+	return 0;
+}
diff --git a/tests/lua.sh b/tests/lua.sh
new file mode 100644
index 00000000..6be797d3
--- /dev/null
+++ b/tests/lua.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+. "${srcdir=.}/init.sh"
+
+SCRIPTFILE=lua-script.lua
+
+run_strace_with_script()
+{
+	cat > "$SCRIPTFILE" || fail_ "cannot write $SCRIPTFILE"
+	run_strace -l "$SCRIPTFILE" "$@"
+}
-- 
2.11.0





More information about the Strace-devel mailing list