From 2d9ba3fe2e79111812f6034ac6df4948bb577f81 Mon Sep 17 00:00:00 2001 From: Matthew LeGendre Date: Wed, 18 Mar 2026 16:19:37 -0700 Subject: [PATCH 1/3] Fix bugs where mixing symlinks and .. in a path can lead to spindle caching the wrong directory --- .../auditserver/ldcs_audit_server_handlers.c | 37 +++--- src/utils/pathfn.c | 111 +++++++++++++++++- src/utils/pathfn.h | 1 + 3 files changed, 132 insertions(+), 17 deletions(-) diff --git a/src/server/auditserver/ldcs_audit_server_handlers.c b/src/server/auditserver/ldcs_audit_server_handlers.c index 8a42f40c..0c1ee0c4 100644 --- a/src/server/auditserver/ldcs_audit_server_handlers.c +++ b/src/server/auditserver/ldcs_audit_server_handlers.c @@ -311,11 +311,11 @@ static int handle_client_myrankinfo_msg(ldcs_process_data_t *procdata, int nc, l **/ static int handle_client_file_request(ldcs_process_data_t *procdata, int nc, ldcs_message_t *msg) { - char *pathname; + char *pathname, *last_slash; char file[MAX_PATH_LEN]; char dir[MAX_PATH_LEN]; int is_stat, is_lstat; - int is_loader, is_dso; + int is_loader, is_dso, dir_ends_w_slash = 0; file[0] = '\0'; dir[0] = '\0'; pathname = msg->data; @@ -330,21 +330,23 @@ static int handle_client_file_request(ldcs_process_data_t *procdata, int nc, ldc debug_printf3("Remapping request of %s --> %s\n", pathname, globalname); pathname = globalname; } - - parseFilenameNoAlloc(pathname, file, dir, MAX_PATH_LEN); - /* do initial check of query, parse the filename and store info */ assert(nc != -1); ldcs_client_t *client = procdata->client_table + nc; - addCWDToDir(client->remote_pid, dir, MAX_PATH_LEN); - reducePath(dir); + + /* do initial check of query, parse the filename and store info */ + parseFilenameNoAlloc2(pathname, file, dir, MAX_PATH_LEN, client->remote_pid); + + last_slash = strrchr(dir, '/'); + dir_ends_w_slash = (last_slash && last_slash[1] == '\0'); GCC7_DISABLE_WARNING("-Wformat-truncation"); GCC7_DISABLE_WARNING("-Wstringop-overflow"); strncpy(client->query_filename, file, MAX_PATH_LEN+1); strncpy(client->query_dirname, dir, MAX_PATH_LEN+1); - snprintf(client->query_globalpath, MAX_PATH_LEN+1, "%s/%s", - client->query_dirname, client->query_filename); + debug_printf3("dirname = '%s'\n", client->query_dirname); + snprintf(client->query_globalpath, MAX_PATH_LEN+1, "%s%s%s", + client->query_dirname, dir_ends_w_slash ? "" : "/", client->query_filename); GCC7_ENABLE_WARNING; GCC7_ENABLE_WARNING; @@ -375,8 +377,8 @@ static handle_file_result_t handle_howto_directory(ldcs_process_data_t *procdata int responsible; cache_dir_result = ldcs_cache_findDirInCache(dir); - debug_printf2("Looked for dir in cache... %s\n", - ldcs_cache_result_to_str(cache_dir_result)); + debug_printf2("Looked for dir %s in cache... %s\n", + dir, ldcs_cache_result_to_str(cache_dir_result)); if (cache_dir_result == LDCS_CACHE_DIR_PARSED_AND_EXISTS) { /* Directory was found */ @@ -2246,24 +2248,27 @@ static int handle_fileexist_test(ldcs_process_data_t *procdata, int nc) static int handle_client_fileexist_msg(ldcs_process_data_t *procdata, int nc, ldcs_message_t *msg) { ldcs_client_t *client; - char *pathname; + char *pathname, *last_slash; char file[MAX_PATH_LEN]; char dir[MAX_PATH_LEN]; + int dir_ends_w_slash; file[0] = '\0'; dir[0] = '\0'; pathname = msg->data; - parseFilenameNoAlloc(pathname, file, dir, MAX_PATH_LEN); assert(nc != -1); client = procdata->client_table + nc; - addCWDToDir(client->remote_pid, dir, MAX_PATH_LEN); - reducePath(dir); + parseFilenameNoAlloc2(pathname, file, dir, MAX_PATH_LEN, client->remote_pid); + + last_slash = strrchr(dir, '/'); + dir_ends_w_slash = (last_slash && last_slash[1] == '\0'); GCC7_DISABLE_WARNING("-Wformat-truncation"); strncpy(client->query_filename, file, MAX_PATH_LEN); strncpy(client->query_dirname, dir, MAX_PATH_LEN); - snprintf(client->query_globalpath, MAX_PATH_LEN, "%s/%s", client->query_dirname, client->query_filename); + snprintf(client->query_globalpath, MAX_PATH_LEN, "%s%s%s", client->query_dirname, + dir_ends_w_slash ? "" : "/", client->query_filename); GCC7_ENABLE_WARNING client->query_localpath = NULL; diff --git a/src/utils/pathfn.c b/src/utils/pathfn.c index ac5ace48..d53b3376 100644 --- a/src/utils/pathfn.c +++ b/src/utils/pathfn.c @@ -73,7 +73,12 @@ int parseFilenameNoAlloc(const char *name, char *file, char *dir, int result_siz if (size >= result_size) size = result_size-1; strncpy(dir, name, size); - dir[size] = '\0'; + if (size == 0 && last_slash == name) { + dir[0] = '/'; + size = 1; + } + dir[size] = '\0'; + } else { strncpy(file, name, result_size); @@ -93,6 +98,9 @@ int reducePath(char *dir) int dir_len = strlen(dir); char tmpdir[MAX_PATH_LEN+1]; + if (strcmp(dir, "/") == 0) + return 0; + while (dir[slash_begin]) { slash_end = slash_begin+1; while (dir[slash_end] != '\0' && dir[slash_end] != '/') slash_end++; @@ -138,3 +146,104 @@ char *concatStrings(const char *str1, int str1_len, const char *str2, int str2_l return buffer; } +static int addCWD(pid_t pid, const char *dir, char *target, int result_size) +{ + char cwd_loc[64]; + int result; + + if (dir[0] == '/') + return 0; + + snprintf(cwd_loc, sizeof(cwd_loc)-1, "/proc/%d/cwd", pid); + result = readlink(cwd_loc, target, result_size-1); + if (result == -1) { + int error = errno; + err_printf("Could not read CWD from %s: %s\n", cwd_loc, strerror(error)); + return -1; + } + return 0; +} + +int parseFilenameNoAlloc2(const char *name, char *file, char *dir, int result_size, pid_t pid) +{ + int result; + int dir_end, is_absolute, cur, last_slash; + int component_start, component_end, component_size; + char path_component[MAX_PATH_LEN]; + + memset(file, 0, result_size); + memset(dir, 0, result_size); + + is_absolute = name[0] == '/'; + + /* Add CWD to 'dir' if we're a relative path */ + if (!is_absolute) { + result = addCWD(pid, name, dir, result_size); + if (result == -1) { + err_printf("Aborting path parsing of %s\n", name); + return -1; + } + dir_end = strlen(dir); + if (dir_end > 1 && dir[dir_end-1] != '/') { + dir[dir_end] = '/'; + dir_end++; + } + } + else { + dir[0] = '/'; + dir_end = 1; + } + + /* Go through each path component in 'name' and add it to 'dir'. Resolve ./ and //// + path components as we do so. + Do not resolve .. path components. That needs readlink access to do correctly. + */ + cur = 0; + while (name[cur] != '\0') { + while (name[cur] == '/') cur++; + component_start = cur; + if (name[component_start] == '\0') + break; + while (name[cur] != '/' && name[cur] != '\0') cur++; + component_end = cur; + component_size = component_end - component_start; + strncpy(path_component, name+component_start, component_size); + path_component[component_size] = '\0'; + + if (strcmp(path_component, ".") == 0) { + //Nothing needed + } + /* + else if (strcmp(path_component, "..") == 0) { + while (dir_end > 1 && dir[dir_end-1] == '/') dir_end--; + while (dir_end > 1 && dir[dir_end-1] != '/') dir_end--; + dir[dir_end] = '\0'; + } + */ + else if (path_component[0] != '\0') { + strncpy(dir+dir_end, path_component, component_size); + dir_end += component_size; + dir[dir_end] = '/'; + dir_end++; + dir[dir_end] = '\0'; + } + } + /* Remove the trailing slash we always added to the end of dir. */ + if (dir_end > 1 && dir[dir_end-1] == '/') + dir[dir_end-1] = '\0'; + + /* Copy the final component out of dir and to file */ + last_slash = dir_end; + while (last_slash > 0 && dir[last_slash] != '/') last_slash--; + strncpy(file, dir+last_slash+1, result_size); + + /* Truncate that final slash that seperated the dir and file + (unless it's a root file system file/dir like "/bin" */ + if (last_slash > 0) + dir[last_slash] = '\0'; + else + dir[1] = '\0'; + + return 0; +} + diff --git a/src/utils/pathfn.h b/src/utils/pathfn.h index c396c1ce..e2fce609 100644 --- a/src/utils/pathfn.h +++ b/src/utils/pathfn.h @@ -24,6 +24,7 @@ extern "C" { #endif int parseFilenameNoAlloc(const char *name, char *file, char *dir, int result_size); +int parseFilenameNoAlloc2(const char *name, char *file, char *dir, int result_size, pid_t pid); int addCWDToDir(pid_t pid, char *dir, int result_size); int reducePath(char *dir); char *concatStrings(const char *str1, int str1_len, const char *str2, int str2_len); From 36e5cd6c4a04635d317b17e869dd17bb3b76651f Mon Sep 17 00:00:00 2001 From: Matthew LeGendre Date: Wed, 18 Mar 2026 16:20:28 -0700 Subject: [PATCH 2/3] Add tests for path parsing and alias handling --- src/logging/spindle_logd.cc | 1 + testsuite/Makefile.am | 7 ++- testsuite/Makefile.in | 8 ++- testsuite/test_driver.c | 105 +++++++++++++++++++++++++++++++++--- 4 files changed, 111 insertions(+), 10 deletions(-) diff --git a/src/logging/spindle_logd.cc b/src/logging/spindle_logd.cc index 62618e62..c1accf3b 100644 --- a/src/logging/spindle_logd.cc +++ b/src/logging/spindle_logd.cc @@ -265,6 +265,7 @@ class TestVerifier { if (strstr(filename, ".so") == NULL && strstr(filename, "retzero") == NULL && + strstr(filename, "bin") == NULL && strstr(filename, ".py") == NULL) return true; bool is_from_temp = (strstr(filename, location) != NULL) && (strncmp(filename, "/__not_exist", 12) != 0); diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am index e663f12c..0ba9638f 100644 --- a/testsuite/Makefile.am +++ b/testsuite/Makefile.am @@ -1,7 +1,7 @@ noinst_PROGRAMS = libgenerator ABS_TEST_DIR = $(abspath $(top_builddir)/testsuite) -BUILT_SOURCES = libtest10.so libtest11.so libtest12.so libtest13.so libtest14.so libtest15.so libtest16.so libtest17.so libtest18.so libtest19.so libtest20.so libtest50.so libtest100.so libtest500.so libtest1000.so libtest2000.so libtest4000.so libtest6000.so libtest8000.so libtest10000.so libtls1.c libtls2.c libtls3.c libtls4.c libtls5.c libtls6.c libtls7.c libtls8.c libtls9.c libtls10.c libtls11.c libtls12.c libtls13.c libtls14.c libtls15.c libtls16.c libtls17.c libtls18.c libtls19.c libtls20.c libsymlink.so libdepC.so libdepB.so libdepA.so libcxxexceptB.so libcxxexceptA.so origin_dir/liboriginlib.so origin_dir/origin_subdir/liborigintarget.so libtestoutput.so libfuncdict.so runTests run_driver run_driver_rm spindle.rc preload_file_list test_driver test_driver_libs retzero_rx retzero_r retzero_x retzero_ badinterp hello_r.py hello_x.py hello_rx.py hello_.py hello_l.py badlink.py spindle_exec_test spindle_deactivated.sh liblocal.so symbind_test interpreter_test interpreter_test_dir/interpreter_test_perl +BUILT_SOURCES = libtest10.so libtest11.so libtest12.so libtest13.so libtest14.so libtest15.so libtest16.so libtest17.so libtest18.so libtest19.so libtest20.so libtest50.so libtest100.so libtest500.so libtest1000.so libtest2000.so libtest4000.so libtest6000.so libtest8000.so libtest10000.so libtls1.c libtls2.c libtls3.c libtls4.c libtls5.c libtls6.c libtls7.c libtls8.c libtls9.c libtls10.c libtls11.c libtls12.c libtls13.c libtls14.c libtls15.c libtls16.c libtls17.c libtls18.c libtls19.c libtls20.c libsymlink.so libdepC.so libdepB.so libdepA.so libcxxexceptB.so libcxxexceptA.so origin_dir/liboriginlib.so origin_dir/origin_subdir/liborigintarget.so libtestoutput.so libfuncdict.so runTests run_driver run_driver_rm spindle.rc preload_file_list test_driver test_driver_libs retzero_rx retzero_r retzero_x retzero_ badinterp hello_r.py hello_x.py hello_rx.py hello_.py hello_l.py badlink.py spindle_exec_test spindle_deactivated.sh liblocal.so symbind_test interpreter_test interpreter_test_dir/interpreter_test_perl alias/aliastest.py if BGQ_BLD DYNAMIC_FLAG=-dynamic @@ -380,6 +380,9 @@ badlink.py: ln -fs noexist.py $@; \ fi +alias/aliastest.py: + $(AM_V_GEN)mkdir -p alias/real1/real2 ; echo "fail" > alias/aliastest.py ; echo "pass" > alias/real1/aliastest.py ; ln -s real1/real2 alias/lnk + spindle_deactivated.sh: spindle_deactivated_template.sh $(AM_V_GEN)cp $< $@; chmod 700 $@ @@ -417,4 +420,4 @@ interpreter_test_dir/interpreter_test_perl: interpreter_test_perl_template inter interpreter_test: interpreter_test_template $(AM_V_GEN)cp $< $@; chmod 700 $@ -CLEANFILES = libtest10.c libtest11.c libtest12.c libtest13.c libtest14.c libtest15.c libtest16.c libtest17.c libtest18.c libtest19.c libtest20.c libtest10.so libtest50.c libtest50.so libtest100.c libtest100.so libtest500.c libtest500.so libtest1000.c libtest1000.so libtest2000.c libtest2000.so libtest4000.c libtest4000.so libtest6000.c libtest6000.so libtest8000.c libtest8000.so libtest10000.c libtest10000.so libsymlink.so libdepA.so libdepB.so libdepC.so libcxxexceptA.so libcxxexceptB.so libtestoutput.so libfuncdict.so runTests run_driver run_driver_rm spindle.rc test_driver test_driver_libs preload_file_list retzero_rx retzero_r retzero_x retzero_ badinterp hello_r.py hello_x.py hello_rx.py hello_.py hello_l.py badlink.py libtls1.c libtls2.c libtls3.c libtls4.c libtls5.c libtls6.c libtls7.c libtls8.c libtls9.c libtls10.c libtls11.c libtls12.c libtls13.c libtls14.c libtls15.c libtls16.c libtls17.c libtls18.c libtls19.c libtls20.c libtls1.so libtls2.so libtls3.so libtls4.so libtls5.so libtls6.so libtls7.so libtls8.so libtls9.so libtls10.so libtls11.so libtls12.so libtls13.so libtls14.so libtls15.so libtls16.so libtls17.so libtls18.so libtls19.so libtls20.so symbind_test libsymbind_a.so libsymbind_b.so libsymbind_c.so libsymbind_d.so libsymbind_e.so libsymbind_f.so libsymbind_g.so interpreter_test interpreter_test_dir/interpreter_test_perl +CLEANFILES = libtest10.c libtest11.c libtest12.c libtest13.c libtest14.c libtest15.c libtest16.c libtest17.c libtest18.c libtest19.c libtest20.c libtest10.so libtest50.c libtest50.so libtest100.c libtest100.so libtest500.c libtest500.so libtest1000.c libtest1000.so libtest2000.c libtest2000.so libtest4000.c libtest4000.so libtest6000.c libtest6000.so libtest8000.c libtest8000.so libtest10000.c libtest10000.so libsymlink.so libdepA.so libdepB.so libdepC.so libcxxexceptA.so libcxxexceptB.so libtestoutput.so libfuncdict.so runTests run_driver run_driver_rm spindle.rc test_driver test_driver_libs preload_file_list retzero_rx retzero_r retzero_x retzero_ badinterp hello_r.py hello_x.py hello_rx.py hello_.py hello_l.py badlink.py libtls1.c libtls2.c libtls3.c libtls4.c libtls5.c libtls6.c libtls7.c libtls8.c libtls9.c libtls10.c libtls11.c libtls12.c libtls13.c libtls14.c libtls15.c libtls16.c libtls17.c libtls18.c libtls19.c libtls20.c libtls1.so libtls2.so libtls3.so libtls4.so libtls5.so libtls6.so libtls7.so libtls8.so libtls9.so libtls10.so libtls11.so libtls12.so libtls13.so libtls14.so libtls15.so libtls16.so libtls17.so libtls18.so libtls19.so libtls20.so symbind_test libsymbind_a.so libsymbind_b.so libsymbind_c.so libsymbind_d.so libsymbind_e.so libsymbind_f.so libsymbind_g.so interpreter_test interpreter_test_dir/interpreter_test_perl alias diff --git a/testsuite/Makefile.in b/testsuite/Makefile.in index 9c225322..9d513046 100644 --- a/testsuite/Makefile.in +++ b/testsuite/Makefile.in @@ -355,7 +355,8 @@ BUILT_SOURCES = libtest10.so libtest11.so libtest12.so libtest13.so \ retzero_r retzero_x retzero_ badinterp hello_r.py hello_x.py \ hello_rx.py hello_.py hello_l.py badlink.py spindle_exec_test \ spindle_deactivated.sh liblocal.so symbind_test \ - interpreter_test interpreter_test_dir/interpreter_test_perl + interpreter_test interpreter_test_dir/interpreter_test_perl \ + alias/aliastest.py @BGQ_BLD_FALSE@DYNAMIC_FLAG = @BGQ_BLD_TRUE@DYNAMIC_FLAG = -dynamic @BGQ_BLD_FALSE@IS_BLUEGENE = false @@ -395,7 +396,7 @@ CLEANFILES = libtest10.c libtest11.c libtest12.c libtest13.c \ libsymbind_a.so libsymbind_b.so libsymbind_c.so \ libsymbind_d.so libsymbind_e.so libsymbind_f.so \ libsymbind_g.so interpreter_test \ - interpreter_test_dir/interpreter_test_perl + interpreter_test_dir/interpreter_test_perl alias all: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) all-am @@ -1055,6 +1056,9 @@ badlink.py: ln -fs noexist.py $@; \ fi +alias/aliastest.py: + $(AM_V_GEN)mkdir -p alias/real1/real2 ; echo "fail" > alias/aliastest.py ; echo "pass" > alias/real1/aliastest.py ; ln -s real1/real2 alias/lnk + spindle_deactivated.sh: spindle_deactivated_template.sh $(AM_V_GEN)cp $< $@; chmod 700 $@ diff --git a/testsuite/test_driver.c b/testsuite/test_driver.c index 13f63fea..acdc54e5 100644 --- a/testsuite/test_driver.c +++ b/testsuite/test_driver.c @@ -827,6 +827,7 @@ static int run_execs() #define STAT 1 #define LSTAT 2 #define FSTAT 4 +#define SPINDLE_STAT 8 static dev_t device; static int run_stat_test(const char *file, int flags, mode_t prot, int expected) { @@ -836,7 +837,7 @@ static int run_stat_test(const char *file, int flags, mode_t prot, int expected) const char *statname = NULL; if (expected == 0) - test_printf("dlstart %s\n", file); + test_printf("dlstart %s\n", file[0] != '/' ? file : file+1); if (flags & LSTAT) { statname = "lstat"; result = lstat(file, &buf); @@ -851,21 +852,25 @@ static int run_stat_test(const char *file, int flags, mode_t prot, int expected) statname = "stat"; result = stat(file, &buf); } + else if (flags & SPINDLE_STAT) { + statname = "spindle_stat"; + result = spindle_stat(file, &buf); + } if (result == -1) result = errno; if (fd != -1) close(fd); if (result != expected) { - err_printf("Expected return value %d, got return value %d from %s test of %s\n", - expected, result, statname, file); + err_printf("Expected return value %d, got return value %d (%s) from %s test of %s\n", + expected, result, strerror(result), statname, file); return -1; } if (result) //Expected error return, do not test buf return 0; - if (buf.st_dev != device) { + if (buf.st_dev != device && !(flags & SPINDLE_STAT)) { err_printf("Expected device %d, got device %d on %s test of %s\n", (int) device, (int) buf.st_dev, statname, file); return -1; @@ -875,7 +880,7 @@ static int run_stat_test(const char *file, int flags, mode_t prot, int expected) prot, buf.st_mode & 0700, statname, file); return -1; } - + return 0; } @@ -937,6 +942,8 @@ static int run_stats() result |= run_stat_test("badlink.py", LSTAT, 0700, 0); result |= run_stat_test(".", LSTAT, 0000, 0); result |= run_stat_test(NULL, LSTAT, 0000, EFAULT); + + result |= run_stat_test("/bin", SPINDLE_STAT, 0000, 0); return result; } @@ -1022,6 +1029,88 @@ static int run_readlinks() return result; } +static int run_alias_test(char *path, int expected_err) { + int fd, result, error; + char buffer[5]; + fd = open(path, O_RDONLY); + if (fd == -1) { + error = errno; + if (!expected_err) { + err_printf("alias_test %s: open expected success, but got error %s (%d)\n", path, strerror(error), error); + return -1; + } + else if (error != expected_err) { + err_printf("alias_test %s: open returned wrong error. Expected %s (%d), got %s (%d)\n", + path, strerror(expected_err), expected_err, strerror(error), error); + return -1; + } + else { + return 0; + } + } + if (fd >= 0 && expected_err) { + err_printf("alias_test %s: open expected error %s (%d), but returned succcess\n", path, strerror(expected_err), expected_err); + close(fd); + return -1; + } + + result = read(fd, buffer, 4); + if (result == -1) { + error = errno; + err_printf("alias_test %s: error reading from buffer: %s (%d)\n", path, strerror(error), error); + close(fd); + return -1; + } + + buffer[4] = '\0'; + if (strcmp(buffer, "pass") != 0) { + err_printf("alias_test %s: Did not read correct file. Got string '%s' from file\n", path, buffer); + close(fd); + return -1; + } + + close(fd); + return 0; +} + +int run_alias_tests() +{ + int result = 0, i; + int slashes_in_cwd = 0; + char path[4096]; + char cwd[4096]; + char slashes[4096]; + + if (chdir_mode) + return 0; + + slashes[0] = '\0'; + getcwd(cwd, 4096); + + for (i = 0; cwd[i] != '\0'; i++) + if (cwd[i] == '/') slashes_in_cwd++; + + for (i = 0; i < slashes_in_cwd + 2; i++) { + strncat(slashes, "../", 4096 - (i * 3)); + } + + result |= run_alias_test("alias/lnk/../aliastest.py", 0); + result |= run_alias_test("./alias/real1/aliastest.py", 0); + result |= run_alias_test("./alias/real1/real2/../aliastest.py", 0); + result |= run_alias_test("./alias////./lnk//../////./aliastest.py", 0); + result |= run_alias_test("alias/lnk/../real2/../aliastest.py", 0); + + snprintf(path, 4096, "%s/alias/real1/aliastest.py", cwd); + result |= run_alias_test(path, 0); + + snprintf(path, 4096, "/%s/alias/real1/aliastest.py", cwd); + result |= run_alias_test(path, 0); + + snprintf(path, 4096, "%s/%s/alias/real1/aliastest.py", slashes, cwd); + result |= run_alias_test(path, 0); + + return result; +} void push_cwd() { @@ -1366,7 +1455,11 @@ int run_test() run_readlinks(); if (had_error) return -1; - + + run_alias_tests(); + if (had_error) + return -1; + check_libraries(); if (had_error) return -1; From ce268158add0928c76d28fde3e6997b45e639133 Mon Sep 17 00:00:00 2001 From: Matthew LeGendre Date: Mon, 23 Mar 2026 13:57:25 -0700 Subject: [PATCH 3/3] WIP on manually calculating static TLS --- .../auditserver/filemngt_calc_static_tls.c | 649 ++++++++++++++++++ .../auditserver/filemngt_calc_static_tls.h | 7 + 2 files changed, 656 insertions(+) create mode 100644 src/server/auditserver/filemngt_calc_static_tls.c create mode 100644 src/server/auditserver/filemngt_calc_static_tls.h diff --git a/src/server/auditserver/filemngt_calc_static_tls.c b/src/server/auditserver/filemngt_calc_static_tls.c new file mode 100644 index 00000000..429de6cc --- /dev/null +++ b/src/server/auditserver/filemngt_calc_static_tls.c @@ -0,0 +1,649 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spindle_debug.h" +#include "filemngt_calc_static_tls.h" + +typedef struct library_list_t { + char *path; + char *interp; + ssize_t tls_size; + struct library_list_t *next; + struct library_list_t *hash_next; +} library_list_t; + +typedef struct executable_list_t { + char *path; + char *ld_library_path; + char *ld_preload; + ssize_t tls_size; + struct executable_list_t *hash_next; +} executable_list_t; + +#if !defined(MAX_PATH_LEN) +#define MAX_PATH_LEN 4096 +#endif + +#define EXECUTABLE_HASH_SIZE 64 +static executable_list_t *executable_hash_table[EXECUTABLE_HASH_SIZE]; +static executable_list_t *executable_hash_lookup(const char *path, const char *ld_library_path, const char *ld_preload); +static executable_list_t *executable_hash_add(const char *path, const char *ld_library_path, const char *ld_preload); +static void library_hash_clean(); +static void executable_hash_clean(); +static void library_list_clean(library_list_t *l); + +#define LIBRARY_HASH_SIZE 512 +static library_list_t *library_hash_table[LIBRARY_HASH_SIZE]; +static library_list_t *library_hash_lookup(const char *path); +static library_list_t *library_hash_add(const char *path); + +static ssize_t static_tls_size_for_dso(const char *path, char *interp, size_t interp_str_size, int *is_script_fd); +static library_list_t *add_to_filelist(library_list_t **head, char *path); +static ssize_t reliable_read(int fd, void *buffer, size_t size); +static library_list_t *get_libraries(const char *executable, const char *ldso, const char *ld_library_path, const char *ld_preload, const char *cwd); +static ssize_t handle_script(const char *path, int fd, const char *ld_library_path, const char *ld_preload, const char *cwd); +static void add_tls_sizes(ssize_t *tls_total, ssize_t tls_new); + +ssize_t calctls_for_executable(const char *executable, const char *ld_library_path, const char *ld_preload, const char *cwd) +{ + executable_list_t *exe; + ssize_t tls_size_exe, tls_size, tls_size_total = 0; + int script_fd = -1; + library_list_t *dt_needed_libs, *i; + + char interp[MAX_PATH_LEN+1]; + interp[0] = '\0'; + + //Check hash table if we already processed this executable/env set + exe = executable_hash_lookup(executable, ld_library_path, ld_preload); + if (exe) + return exe->tls_size; + + //Look up TLS size and interpreter for the executable + tls_size_exe = static_tls_size_for_dso(executable, interp, sizeof(interp), + &script_fd); + if (tls_size_exe == -1) { + err_printf("Could not parse executable %s\n", executable); + return -1; + } + if (script_fd != -1) { + //This was a script that started with #! + return handle_script(executable, script_fd, ld_library_path, ld_preload, cwd); + } + if (interp[0] == '\0') { + err_printf("%s was not a dynamic executable\n", executable); + return -1; + } + + add_tls_sizes(&tls_size_total, tls_size_exe); + + //Get the statically-determinable list of libraries this exe depends on + dt_needed_libs = get_libraries(executable, interp, ld_library_path, ld_preload, cwd); + if (!dt_needed_libs) { + err_printf("%s could not parse library list\n", executable); + return -1; + } + + for (i = dt_needed_libs; i != NULL; i = i->next) { + tls_size = static_tls_size_for_dso(i->path, NULL, 0, NULL); + if (tls_size == -1) { + continue; + } + add_tls_sizes(&tls_size_total, tls_size); + } + library_list_clean(dt_needed_libs); + + return tls_size_total; +} + +static ssize_t handle_script(const char *path, int fd, const char *ld_library_path, const char *ld_preload, const char *cwd) +{ + char interp[MAX_PATH_LEN+1]; + char *line = NULL; + size_t linesize = 0, i, j; + ssize_t glresult, tls_size; + executable_list_t *entry; + + FILE *f = fdopen(fd, "r"); + if (!f) { + close(fd); + err_printf("Error reopening FILE for %s\n", path); + return -1; + } + + glresult = getline(&line, &linesize, f); + if (glresult == -1) { + err_printf("Error reading line in script for %s\n", path); + fclose(f); + return -1; + } + fclose(f); + + //skip '#!' and then initial spaces + for (i = 0; i < linesize && (line[i] == ' ' || line[i] == '\t'); i++); + for (j = 0; + j < sizeof(interp) && i < linesize && line[i] != ' ' && line[i] != '\t' && line[i] != '\n'; + j++, i++) + { + interp[j] = line[i]; + } + if (j == sizeof(interp)) + j--; + interp[j] = '\0'; + + //We have the hashbang string after the intepreter. Re-run the whole + //calculation on that. + tls_size = calctls_for_executable(interp, ld_library_path, ld_preload, cwd); + + //Associate the TLS size from the interpreter with the script + entry = executable_hash_add(path, ld_library_path, ld_preload); + entry->tls_size = tls_size; + + free(line); + return tls_size; +} + +static ssize_t static_tls_size_for_dso(const char *path, char *interp, size_t interp_str_size, int *is_script_fd) +{ + int fd = -1, i, has_static_tls = 0; + void *buffer = NULL; + char *cbuffer; + ssize_t result; + off_t oresult; + ElfW(Ehdr) *ehdr; + ElfW(Phdr) *phdr, *phdrs; + ElfW(Half) phnum, phentsize; + ElfW(Off) phoff = 0, dynamic_off = 0, interp_off = 0; + ElfW(Xword) dynamic_size = 0, interp_size = 0; + ElfW(Dyn) *dyn = NULL, *end_dyn; + ssize_t tls_size = 0, return_result = -1; + library_list_t *lib; + char ldso[MAX_PATH_LEN+1]; + ldso[0] = '\0'; + + //Check cache if library has already been parsed. + lib = library_hash_lookup(path); + if (lib) { + if (interp) { + strncpy(interp, lib->interp, interp_str_size); + } + return lib->tls_size; + } + + fd = open(path, O_RDONLY); + if (fd == -1 && path[0] == '/') { + err_printf("Could not open '%s'\n", path); + goto done; + } + else if (fd == -1) { + goto done; + } + + buffer = (void *) malloc(sizeof(ElfW(Ehdr))); + cbuffer = (char *) buffer; + + //First check the initial two characters for script hashbang '#!' + result = reliable_read(fd, buffer, 2); + if (result < 2) { + err_printf("Not an executable or script %s\n", path); + goto done; + } + else if (cbuffer[0] == '#' && cbuffer[1] == '!') { + *is_script_fd = fd; + free(buffer); + return 0; + } + else if (cbuffer[0] == ELFMAG0 && cbuffer[1] == ELFMAG1) { + //Fall through to the below DSO handling + } + else { + err_printf("Not an executable or script %s\n", path); + goto done; + } + + //Read an elf header (minus the two bytes we already read) + result = reliable_read(fd, cbuffer+2, sizeof(ElfW(Ehdr))-2); + if (result == -1) { + err_printf("Could not read elf eheader %s\n", path); + goto done; + } + if (result != sizeof(ElfW(Ehdr))-2) { + err_printf("Not an executable or script %s\n", path); + return -1; + } + + ehdr = (ElfW(Ehdr) *) buffer; + if (ehdr->e_ident[0] != ELFMAG0 || ehdr->e_ident[1] != ELFMAG1 || + ehdr->e_ident[2] != ELFMAG2 || ehdr->e_ident[3] != ELFMAG3) { + err_printf("Is not an elf file or script %s\n", path); + goto done; + } + + phnum = ehdr->e_phnum; + phentsize = ehdr->e_phentsize; + phoff = ehdr->e_phoff; + + free(buffer); + + //Using info from the elf header, seek to and read the program headers. + buffer = malloc(phentsize * phnum); + oresult = lseek(fd, phoff, SEEK_SET); + if (result == -1) { + err_printf("Could not seek to program headers %s\n", path); + goto done; + } + result = reliable_read(fd, buffer, phentsize * phnum); + if (result == -1) { + err_printf("Could not read program header %s\n", path); + goto done; + } + + //Iterate through program headers for necessary info (TLS size, + // where's the interpreter string, where's the dynamic section. + phdrs = (ElfW(Phdr) *) buffer; + for (i = 0; i < phnum; i++) { + phdr = phdrs + i; + if (phdr->p_type == PT_TLS) { + tls_size = phdr->p_memsz; + } + if (phdr->p_type == PT_DYNAMIC) { + dynamic_off = phdr->p_offset; + dynamic_size = phdr->p_filesz; + } + if (phdr->p_type == PT_INTERP) { + interp_off = phdr->p_offset; + interp_size = phdr->p_filesz; + } + } + free(buffer); + buffer = NULL; + + if (!dynamic_off && !dynamic_size) { + err_printf("Is a static binary: %s\n", path); + goto done; + } + + //Read the interpreter string. + if (interp_off && interp_size) { + oresult = lseek(fd, interp_off, SEEK_SET); + if (oresult == -1) { + err_printf("Could not seek to interpreter for %s\n", path); + goto done; + } + + if (interp_size >= sizeof(ldso)) + interp_size = sizeof(ldso) - 1; + result = reliable_read(fd, ldso, interp_size); + if (result == -1) { + err_printf("Could not read interpreter for %s\n", path); + goto done; + } + ldso[interp_size] = '\0'; + } + + //Read the dynamic section. + buffer = malloc(dynamic_size); + + oresult = lseek(fd, dynamic_off, SEEK_SET); + if (oresult == -1) { + err_printf("Could not seek to dynamic section for %s\n", path); + goto done; + } + + result = reliable_read(fd, buffer, dynamic_size); + if (result == -1) { + err_printf("Could not read dynamic section for %s\n", path); + goto done; + } + + //Look through the dynamic section for the DT_FLAGS and if DT_STATIC_TLS + // is set in it. + end_dyn = (ElfW(Dyn) *) (((unsigned char *) buffer) + dynamic_size); + dyn = (ElfW(Dyn) *) buffer; + for (dyn = (ElfW(Dyn) *) buffer; (dyn->d_tag != DT_NULL) && (dyn < end_dyn); dyn++) { + if (dyn->d_tag == DT_FLAGS) { + has_static_tls = (dyn->d_un.d_val & DF_STATIC_TLS) ? 1 : 0; + break; + } + } + + if (!has_static_tls) + tls_size = 0; + + return_result = tls_size; + + done: + lib = library_hash_add(path); + lib->tls_size = return_result; + lib->interp = ldso[0] ? strdup(ldso) : ""; + if (interp) { + strncpy(interp, ldso, interp_size); + } + + if (buffer) + free(buffer); + if (fd != -1) + close(fd); + return return_result; +} + +static library_list_t *add_to_filelist(library_list_t **head, char *path) +{ + library_list_t *newfile; + + newfile = (library_list_t *) malloc(sizeof(library_list_t)); + newfile->path = strdup(path); + newfile->interp = NULL; + newfile->tls_size = 0; + newfile->next = *head; + newfile->hash_next = NULL; + *head = newfile; + + return newfile; +} + +#define PRD 0 +#define PWR 1 +static library_list_t *get_libraries(const char *executable, const char *ldso, const char *ld_library_path, const char *ld_preload, const char *cwd) +{ + int pipe_fd[2]; + int result, status; + ssize_t sresult; + const char* args[4]; + char *line = NULL; + size_t line_size = 0; + pid_t pid; + FILE *f = NULL; + library_list_t *filelist = NULL; + + args[0] = ldso; + args[1] = "--list"; + args[2] = executable; + args[3] = NULL; + pipe_fd[0] = pipe_fd[1] = -1; + + result = pipe(pipe_fd); + if (result == -1) { + err_printf("Error creating pipe\n"); + goto done; + } + + pid = fork(); + if (pid == -1) { + err_printf("Error forking new process\n"); + goto done; + } + if (pid == 0) { + close(pipe_fd[PRD]); + if (ld_library_path) + setenv("LD_LIBRARY_PATH", ld_library_path, 1); + else + unsetenv("LD_LIBRARY_PATH"); + if (ld_preload) + setenv("LD_PRELOAD", ld_preload, 1); + else + unsetenv("LD_PRELOAD"); + if (cwd) + chdir(cwd); + dup2(pipe_fd[PWR], 1); + dup2(pipe_fd[PWR], 2); + execv(args[0], (char * const*) args); + err_printf("Error execing ld.so %s\n", ldso); + exit(-1); + } + close(pipe_fd[PWR]); + pipe_fd[PWR] = -1; + result = waitpid(pid, &status, 0); + if (result == -1) { + err_printf("Error during waitpid on %s --list %s: %s\n", + ldso, executable, strerror(errno)); + goto done; + } + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + err_printf("App exited with error on %d %s\n", + (int) WEXITSTATUS(status), ldso); + goto done; + } + if (WIFSIGNALED(status)) { + err_printf("App exited with signal %d on %s\n", + (int) WTERMSIG(status), ldso); + goto done; + } + if (!WIFEXITED(status)) { + err_printf("App did not exit via status %d on %s\n", + (int) status, ldso); + goto done; + } + + f = fdopen(pipe_fd[PRD], "r"); + if (!f) { + err_printf("Could not convert pipe fd to stream\n"); + goto done; + } + + for (;;) { + char *arrow, *paren, *start, *end; + sresult = getline(&line, &line_size, f); + if (sresult == -1) + break; + start = line; + //skip whitespace and blank lines + while ((*start == '\t' || *start == ' ') && (*start != '\0')) + start++; + if (*start == '\0') + continue; + arrow = strstr(line, " => "); + if (arrow) { + start = arrow + 4; + if (!*start) { + err_printf("parse error at arrow line %s\n", line); + continue; + } + } + paren = strrchr(start, '('); + end = paren ? paren-1 : line+strlen(line)-1; + while ((*end == '\t' || *end == ' ') && (end != start)) end--; + end++; + *end = '\0'; + + add_to_filelist(&filelist, start); + }; + + done: + if (f) { + fclose(f); + } + else if (pipe_fd[PRD] != -1) { + close(pipe_fd[PRD]); + } + if (pipe_fd[PWR] != -1) { + close(pipe_fd[PRD]); + } + if (line) { + free(line); + } + + return filelist; +} + +static unsigned long hash_func(const char *path, const char *env1, const char *env2) +{ + unsigned long hash = 5381; + int c; + + if (path) { + while ((c = *path++)) + hash = ((hash << 5) + hash) + c; + } + if (env1) { + while ((c = *env1++)) + hash = ((hash << 5) + hash) + c; + } + if (env2) { + while ((c = *env2++)) + hash = ((hash << 5) + hash) + c; + } + return hash; +} + +static executable_list_t *executable_hash_lookup(const char *path, const char *ld_library_path, const char *ld_preload) +{ + unsigned long key; + executable_list_t *i; + + key = hash_func(path, ld_library_path, ld_preload) % EXECUTABLE_HASH_SIZE; + for (i = executable_hash_table[key]; i != NULL; i = i->hash_next) { + if ((strcmp(path, i->path) == 0) && (strcmp(ld_library_path, i->ld_library_path) == 0) && + (strcmp(ld_preload, i->ld_preload) == 0)) + { + return i; + } + } + return NULL; +} + +static executable_list_t *executable_hash_add(const char *path, const char *ld_library_path, const char *ld_preload) +{ + unsigned long key; + executable_list_t *newh; + + key = hash_func(path, ld_library_path, ld_preload) % EXECUTABLE_HASH_SIZE; + newh = (executable_list_t *) malloc(sizeof(executable_list_t)); + newh->path = strdup(path); + newh->ld_library_path = ld_library_path ? strdup(ld_library_path) : ""; + newh->ld_preload = ld_preload ? strdup(ld_preload) : ""; + newh->hash_next = executable_hash_table[key]; + executable_hash_table[key] = newh; + + return newh; +} + +static library_list_t *library_hash_lookup(const char *path) +{ + unsigned long key; + library_list_t *i; + + key = hash_func(path, NULL, NULL) % LIBRARY_HASH_SIZE; + for (i = library_hash_table[key]; i != NULL; i = i->hash_next) { + if (strcmp(path, i->path) == 0) { + return i; + } + } + return NULL; +} + +static library_list_t *library_hash_add(const char *path) +{ + unsigned long key; + library_list_t *newh; + + key = hash_func(path, NULL, NULL) % LIBRARY_HASH_SIZE; + newh = (library_list_t *) malloc(sizeof(library_list_t)); + newh->path = strdup(path); + newh->hash_next = library_hash_table[key]; + library_hash_table[key] = newh; + + return newh; +} + +static ssize_t reliable_read(int fd, void *buffer, size_t size) +{ + ssize_t bytes_read = 0, result; + int retry_count = 10, error; + + do { + result = read(fd, ((unsigned char *) buffer) + bytes_read, size - bytes_read); + if (result == -1) { + if (errno == EINTR) { + continue; + } + else if (errno == EAGAIN && retry_count-- >= 0) { + continue; + } + error = errno; + err_printf("read returned error %d\n", error); + errno = error; + return -1; + } + else if (result == 0) { + return bytes_read; + } + bytes_read += result; + } while (bytes_read < size); + + return bytes_read; +} + +static void add_tls_sizes(ssize_t *tls_total, ssize_t tls_new) +{ + *tls_total += tls_new; +} + + +static void library_list_clean(library_list_t *l) +{ + library_list_t *j, *next; + next = NULL; + for (j = l; j != NULL; j = next) { + next = j->next; + free(j->path); + if (j->interp && j->interp[0]) + free(j->interp); + free(j); + } +} + +static void library_hash_clean() +{ + int i; + library_list_t *j, *next; + next = NULL; + for (i = 0; i < LIBRARY_HASH_SIZE; i++) { + for (j = library_hash_table[i]; j != NULL; j = next) { + next = j->hash_next; + free(j->path); + if (j->interp && j->interp[0]) + free(j->interp); + j->hash_next = NULL; + free(j); + } + library_hash_table[i] = NULL; + } +} + +static void executable_hash_clean() +{ + int i; + executable_list_t *j, *next; + next = NULL; + for (i = 0; i < EXECUTABLE_HASH_SIZE; i++) { + for (j = executable_hash_table[i]; j != NULL; j = next) { + next = j->hash_next; + if (j->path && j->path[0]) { + free(j->path); + } + if (j->ld_library_path && j->ld_library_path[0]) { + free(j->ld_library_path); + } + if (j->ld_preload && j->ld_preload[0]) { + free(j->ld_preload); + } + free(j); + } + } +} + +void calctls_clean_caches() +{ + library_hash_clean(); + executable_hash_clean(); +} + diff --git a/src/server/auditserver/filemngt_calc_static_tls.h b/src/server/auditserver/filemngt_calc_static_tls.h new file mode 100644 index 00000000..ddad1fb9 --- /dev/null +++ b/src/server/auditserver/filemngt_calc_static_tls.h @@ -0,0 +1,7 @@ +#if !defined(_FILEMNGT_CALC_STATIC_TLS_H_) +#define _FILEMNGT_CALC_STATIC_TLS_H_ + +ssize_t calctls_for_executable(const char *executable, const char *ld_library_path, const char *ld_preload, const char *cwd); +void calctls_clean_caches(); + +#endif