From 6d3bc339f56aba5e07ba9caf8d6ee69c18dd2a2f Mon Sep 17 00:00:00 2001 From: Chun-Hung Tseng Date: Wed, 22 Nov 2023 23:59:39 +0100 Subject: [PATCH] Unit test for path sanitation porting Reference: https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/path/path_test.go --- .gitignore | 1 + mk/tests.mk | 36 +++++++++++++++++++- src/elf.c | 4 +-- src/main.c | 1 + src/utils.c | 38 ++++++++++----------- src/utils.h | 4 ++- tests/path/test-path.c | 75 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 tests/path/test-path.c diff --git a/.gitignore b/.gitignore index f7efc6140..212a53248 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ build/mini-gdbstub build/softfloat build/cache/ build/map/ +build/path/ *.o *.o.d tests/**/*.elf diff --git a/mk/tests.mk b/mk/tests.mk index 6b9158c8c..8d11e336b 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -6,6 +6,10 @@ MAP_TEST_SRCDIR := tests/map MAP_TEST_OUTDIR:= build/map MAP_TEST_TARGET := $(MAP_TEST_OUTDIR)/test-map +PATH_TEST_SRCDIR := tests/path +PATH_TEST_OUTDIR := build/path +PATH_TEST_TARGET := $(PATH_TEST_OUTDIR)/test-path + CACHE_TEST_OBJS := \ test-cache.o @@ -13,6 +17,9 @@ MAP_TEST_OBJS := \ test-map.o \ mt19937.o +PATH_TEST_OBJS := \ + test-path.o + CACHE_TEST_OBJS := $(addprefix $(CACHE_TEST_OUTDIR)/, $(CACHE_TEST_OBJS)) \ $(OUT)/cache.o $(OUT)/mpool.o OBJS += $(CACHE_TEST_OBJS) @@ -23,6 +30,11 @@ MAP_TEST_OBJS := $(addprefix $(MAP_TEST_OUTDIR)/, $(MAP_TEST_OBJS)) \ OBJS += $(MAP_TEST_OBJS) deps += $(MAP_TEST_OBJS:%.o=%.o.d) +PATH_TEST_OBJS := $(addprefix $(PATH_TEST_OUTDIR)/, $(PATH_TEST_OBJS)) \ + $(OUT)/utils.o +OBJS += $(PATH_TEST_OBJS) +deps += $(PATH_TEST_OBJS:%.o=%.o.d) + # Check adaptive replacement cache policy is enabled or not, default is LFU ifeq ($(ENABLE_ARC), 1) CACHE_TEST_ACTIONS := \ @@ -43,8 +55,9 @@ endif CACHE_TEST_OUT = $(addprefix $(CACHE_TEST_OUTDIR)/, $(CACHE_TEST_ACTIONS:%=%.out)) MAP_TEST_OUT = $(MAP_TEST_TARGET).out +PATH_TEST_OUT = $(PATH_TEST_TARGET).out -tests : run-test-cache run-test-map +tests : run-test-cache run-test-map run-test-path run-test-cache: $(CACHE_TEST_OUT) $(Q)$(foreach e,$(CACHE_TEST_ACTIONS),\ @@ -66,6 +79,15 @@ run-test-map: $(MAP_TEST_OUT) $(PRINTF) "Failed.\n"; \ fi; +run-test-path: $(PATH_TEST_OUT) + $(Q)$(PATH_TEST_TARGET) + $(VECHO) "Running test-path ... "; \ + if [ $$? -eq 0 ]; then \ + $(call notice, [OK]); \ + else \ + $(PRINTF) "Failed.\n"; \ + fi; + $(CACHE_TEST_OUT): $(CACHE_TEST_TARGET) $(Q)$(foreach e,$(CACHE_TEST_ACTIONS),\ $(CACHE_TEST_TARGET) $(CACHE_TEST_SRCDIR)/$(e).in > $(CACHE_TEST_OUTDIR)/$(e).out; \ @@ -91,3 +113,15 @@ $(MAP_TEST_OUTDIR)/%.o: $(MAP_TEST_SRCDIR)/%.c $(VECHO) " CC\t$@\n" $(Q)mkdir -p $(dir $@) $(Q)$(CC) -o $@ $(CFLAGS) -I./src -c -MMD -MF $@.d $< + +$(PATH_TEST_OUT): $(PATH_TEST_TARGET) + $(Q)touch $@ + +$(PATH_TEST_TARGET): $(PATH_TEST_OBJS) + $(VECHO) " CC\t$@\n" + $(Q)$(CC) $^ -o $@ + +$(PATH_TEST_OUTDIR)/%.o: $(PATH_TEST_SRCDIR)/%.c + $(VECHO) " CC\t$@\n" + $(Q)mkdir -p $(dir $@) + $(Q)$(CC) -o $@ $(CFLAGS) -I./src -c -MMD -MF $@.d $< diff --git a/src/elf.c b/src/elf.c index d483a6801..8cd9360e7 100644 --- a/src/elf.c +++ b/src/elf.c @@ -291,13 +291,13 @@ bool elf_load(elf_t *e, riscv_t *rv, memory_t *mem) return true; } -bool elf_open(elf_t *e, const char *orig_path) +bool elf_open(elf_t *e, const char *input) { /* free previous memory */ if (e->raw_data) release(e); - char *path = sanitize_path(orig_path); + char *path = sanitize_path(input); #if defined(USE_MMAP) int fd = open(path, O_RDONLY); diff --git a/src/main.c b/src/main.c index 6c4d86d39..7f35034c4 100644 --- a/src/main.c +++ b/src/main.c @@ -10,6 +10,7 @@ #include "elf.h" #include "state.h" +#include "utils.h" /* enable program trace mode */ static bool opt_trace = false; diff --git a/src/utils.c b/src/utils.c index 962430d35..127479015 100644 --- a/src/utils.c +++ b/src/utils.c @@ -72,9 +72,9 @@ void rv_clock_gettime(struct timespec *tp) tp->tv_nsec = tv_usec / 1000; /* Transfer to microseconds */ } -char *sanitize_path(const char *orig_path) +char *sanitize_path(const char *input) { - size_t n = strnlen(orig_path, MAX_PATH_LEN); + size_t n = strnlen(input, MAX_PATH_LEN); char *ret = malloc(MAX_PATH_LEN + 1); memset(ret, '\0', MAX_PATH_LEN + 1); @@ -86,15 +86,15 @@ char *sanitize_path(const char *orig_path) return ret; } - int rooted = (orig_path[0] == '/'); + int rooted = (input[0] == '/'); /* * Invariants: - * reading from path; r is index of next byte to process -> path[r] - * writing to buf; w is index of next byte to write -> ret[strlen(ret)] - * dotdot is index in buf where .. must stop, either because - * a) it is the leading slash - * b) it is a leading ../../.. prefix. + * reading from path; r is index of next byte to process -> path[r] + * writing to buf; w is index of next byte to write -> ret[strlen(ret)] + * dotdot is index in buf where .. must stop, either because: + * a) it is the leading slash + * b) it is a leading ../../.. prefix. */ size_t w = 0; size_t r = 0; @@ -107,15 +107,15 @@ char *sanitize_path(const char *orig_path) } while (r < n) { - if (orig_path[r] == '/') { + if (input[r] == '/') { /* empty path element */ r++; - } else if (orig_path[r] == '.' && - (r + 1 == n || orig_path[r + 1] == '/')) { + } else if (input[r] == '.' && + (r + 1 == n || input[r + 1] == '/')) { /* . element */ r++; - } else if (orig_path[r] == '.' && orig_path[r + 1] == '.' && - (r + 2 == n || orig_path[r + 2] == '/')) { + } else if (input[r] == '.' && input[r + 1] == '.' && + (r + 2 == n || input[r + 2] == '/')) { /* .. element: remove to last / */ r += 2; @@ -146,13 +146,11 @@ char *sanitize_path(const char *orig_path) } /* copy element */ - for (; r < n && orig_path[r] != '/'; r++) { - ret[w] = orig_path[r]; + for (; r < n && input[r] != '/'; r++) { + ret[w] = input[r]; w++; } } - // printf("w = %ld, r = %ld, dotdot = %ld\nret = %s\n", w, r, dotdot, - // ret); } /* Turn empty string into "." */ @@ -161,8 +159,8 @@ char *sanitize_path(const char *orig_path) w++; } - for (size_t i = w; i < n; i++) { - ret[i] = '\0'; - } + /* starting from w till the end, we should mark it as \0 since that part of the buffer is not used */ + memset(ret + w, '\0', sizeof(MAX_PATH_LEN + 1 - w)); + return ret; } diff --git a/src/utils.h b/src/utils.h index fa11e66c1..0306b4a4e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -46,4 +46,6 @@ void rv_clock_gettime(struct timespec *tp); * Getting Dot-Dot Right,” * https://9p.io/sys/doc/lexnames.html */ -char *sanitize_path(const char *orig_path); +char *sanitize_path(const char *input); + +void sanitize_path_test(); diff --git a/tests/path/test-path.c b/tests/path/test-path.c new file mode 100644 index 000000000..8fd3b93e1 --- /dev/null +++ b/tests/path/test-path.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include "utils.h" + +void compare(char *input, char *expected_output) +{ + char *input_sanitized = sanitize_path(input); + if (strcmp(input_sanitized, expected_output)) { + printf("\n\nInput =\t\t\t%s\nOutput =\t\t%s\nExpected output =\t%s\n", + input, input_sanitized, expected_output); + + exit(1); + } + + free(input_sanitized); +} + +void sanitize_path_test() +{ + /* Already clean */ + compare("", "."); + compare("abc", "abc"); + compare("abc/def", "abc/def"); + compare(".", "."); + compare("..", ".."); + compare("../..", "../.."); + compare("../../abc", "../../abc"); + compare("/abc", "/abc"); + compare("/", "/"); + + /* Remove trailing slash */ + compare("abc/", "abc"); + compare("abc/def/", "abc/def"); + compare("a/b/c/", "a/b/c"); + compare("./", "."); + compare("../", ".."); + compare("../../", "../.."); + compare("/abc/", "/abc"); + + /* Remove doubled slash */ + compare("abc//def//ghi", "abc/def/ghi"); + compare("//abc", "/abc"); + compare("///abc", "/abc"); + compare("//abc//", "/abc"); + compare("abc//", "abc"); + + /* Remove . elements */ + compare("abc/./def", "abc/def"); + compare("/./abc/def", "/abc/def"); + compare("abc/.", "abc"); + + /* Remove .. elements */ + compare("abc/def/ghi/../jkl", "abc/def/jkl"); + compare("abc/def/../ghi/../jkl", "abc/jkl"); + compare("abc/def/..", "abc"); + compare("abc/def/../..", "."); + compare("/abc/def/../..", "/"); + compare("abc/def/../../..", ".."); + compare("/abc/def/../../..", "/"); + compare("abc/def/../../../ghi/jkl/../../../mno", "../../mno"); + + /* Combinations */ + compare("abc/./../def", "def"); + compare("abc//./../def", "def"); + compare("abc/../../././../def", "../../def"); +} + +int main() +{ + sanitize_path_test(); + + return 0; +} \ No newline at end of file