diff --git a/cmake/ProjectOptions.cmake b/cmake/ProjectOptions.cmake index 4fb8bfd6..24261a01 100644 --- a/cmake/ProjectOptions.cmake +++ b/cmake/ProjectOptions.cmake @@ -31,6 +31,10 @@ function(ss_project_options target) # it when they are rewritten. target_compile_options(${target} PRIVATE $<$:-fno-exceptions -fno-rtti -fno-threadsafe-statics>) + # libstdc++ 15 enables its assertions at -O0, and they call + # __glibcxx_assert_fail from the C++ runtime, which the programs and + # libshadowsocks-c must not need (scripts/check_cxx_runtime.py). + target_compile_definitions(${target} PRIVATE $<$:_GLIBCXX_NO_ASSERTIONS>) # GCC's C++ -Wall adds -Wsign-compare and -Wclass-memaccess, which C's # -Wall does not have. Modules converted from C keep the warning set # they were written against; ss_strict_warnings() turns both back on. diff --git a/src/manager.cpp b/src/manager.cpp index 633cd54b..f52edb27 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -115,13 +116,15 @@ udp_sender_uid(const struct sockaddr_storage *from, uid_t *uid) if (fgets(line, sizeof(line), f) != NULL) { // header while (!found && fgets(line, sizeof(line), f) != NULL) { char local[64]; - unsigned port; - unsigned long owner; + char port_hex[9]; + char owner_dec[21]; // sl local_address rem_address st tx:rx tr:when retrnsmt uid - if (sscanf(line, " %*s %63[0-9A-Fa-f]:%x %*s %*s %*s %*s %*s %lu", local, &port, &owner) != 3 - || port != want_port) { + if (sscanf(line, " %*s %63[0-9A-Fa-f]:%8[0-9A-Fa-f] %*s %*s %*s %*s %*s %20[0-9]", + local, port_hex, owner_dec) != 3 + || strtoul(port_hex, NULL, 16) != want_port) { continue; } + unsigned long owner = strtoul(owner_dec, NULL, 10); // The kernel prints each 32-bit word of the address as a host // order integer; zeros mean the socket is unbound (wildcard). uint8_t bytes[16]; @@ -1041,13 +1044,15 @@ is_managed_server(int pid) char cmdline[4096]; size_t n = fread(cmdline, 1, sizeof(cmdline), f); fclose(f); - for (size_t i = 0; i < n;) { - const char *arg = cmdline + i; - size_t len = strnlen(arg, n - i); - if (len == strlen("--manager-address") && memcmp(arg, "--manager-address", len) == 0) { + // NUL-separated arguments; the last one may be cut short. + std::string_view rest(cmdline, n); + while (!rest.empty()) { + std::string_view::size_type nul = rest.find('\0'); + std::string_view arg(rest.data(), nul != std::string_view::npos ? nul : rest.size()); + if (arg == "--manager-address") { return true; } - i += len + 1; + rest.remove_prefix(nul != std::string_view::npos ? nul + 1 : rest.size()); } return false; #else diff --git a/src/process.cpp b/src/process.cpp index d8ffa1a0..c62299e9 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -4,12 +4,16 @@ #include #include #include "utils.h" +#include +#include +#include #ifdef _WIN32 #include #include #else #include #include +#include #include #include #include @@ -118,32 +122,51 @@ ss_process_user(struct ss_process *process, const char *user) /* The executable execve() runs: `program` itself if it names a path, or the * first match on PATH, as posix_spawnp() would find it. */ -static char * -find_program(const char *program) +using ProgramPath = std::array; + +static bool +set_path(ProgramPath &out, std::string_view dir, std::string_view program) { - if (strchr(program, '/') != NULL) { - return copy_string(program); + std::string_view slash = dir.empty() ? "" : "/"; + if (dir.size() + slash.size() + program.size() >= out.size()) { + return false; } - const char *path = getenv("PATH"); - if (path == NULL || *path == '\0') { + auto end = std::copy(dir.begin(), dir.end(), out.begin()); + end = std::copy(slash.begin(), slash.end(), end); + end = std::copy(program.begin(), program.end(), end); + *end = '\0'; + return true; +} + +static std::string_view +env_value(const char *name) +{ + const char *value = getenv(name); + return value != NULL ? std::string_view(value) : std::string_view(); +} + +static bool +find_program(std::string_view program, ProgramPath &out) +{ + if (program.find('/') != std::string_view::npos) { + return set_path(out, {}, program); + } + std::string_view path = env_value("PATH"); + if (path.empty()) { path = "/usr/local/bin:/usr/bin:/bin"; } - size_t room = strlen(path) + strlen(program) + 2; - char *candidate = (char *)ss_malloc(room); // heap-ok: plugin start, startup - for (const char *dir = path;; ) { - const char *colon = strchr(dir, ':'); - size_t len = colon != NULL ? (size_t)(colon - dir) : strlen(dir); - snprintf(candidate, room, "%.*s%s%s", (int)len, dir, len > 0 ? "/" : "", program); - if (access(candidate, X_OK) == 0) { - return candidate; + for (;;) { + // Not substr(): it can throw, which would pull in the C++ runtime. + std::string_view::size_type colon = path.find(':'); + std::string_view dir(path.data(), colon != std::string_view::npos ? colon : path.size()); + if (set_path(out, dir, program) && access(out.data(), X_OK) == 0) { + return true; } - if (colon == NULL) { - break; + if (colon == std::string_view::npos) { + return false; } - dir = colon + 1; + path.remove_prefix(colon + 1); } - free(candidate); // heap-ok: plugin start, startup - return NULL; } /* In the forked child: lose root, but keep the right to bind ports below @@ -180,16 +203,14 @@ drop_in_child(const struct ss_process *process) static int fork_as_user(struct ss_process *process, char **env) { - char *path = find_program(process->program); - if (path == NULL) { + ProgramPath path; + if (!find_program(process->program, path)) { return ENOENT; } /* The child reports a failed switch or exec through this pipe. */ int report[2]; if (pipe(report) != 0) { - int error = errno; - free(path); // heap-ok: plugin start, startup - return error; + return errno; } (void)fcntl(report[1], F_SETFD, FD_CLOEXEC); pid_t pid = fork(); @@ -204,7 +225,7 @@ fork_as_user(struct ss_process *process, char **env) signal(SIGINT, SIG_DFL); int error = EPERM; if (drop_in_child(process) == 0) { - execve(path, process->argv, env); // NOLINT(clang-analyzer-optin.taint.GenericTaint): the PATH lookup posix_spawnp() would do + execve(path.data(), process->argv, env); // NOLINT(clang-analyzer-optin.taint.GenericTaint): the PATH lookup posix_spawnp() would do error = errno; } ssize_t ignored = write(report[1], &error, sizeof(error)); @@ -227,7 +248,6 @@ fork_as_user(struct ss_process *process, char **env) } } close(report[0]); - free(path); // heap-ok: plugin start, startup return error; } diff --git a/src/ssurl.cpp b/src/ssurl.cpp index c23f9f14..b79719e1 100644 --- a/src/ssurl.cpp +++ b/src/ssurl.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "base64.h" #include "ssurl.h" @@ -253,9 +254,9 @@ ss_url_parse(const char *uri, ss_url_t *url) url->tag = percent_decode(hash + 1, body_len - (size_t)(hash - body) - 1); /* It is only logged: no control characters (newlines, escapes) * from a link may reach the log. */ - for (char *c = url->tag; *c != '\0'; c++) { - if ((unsigned char)*c < 0x20 || *c == 0x7f) { - *c = '?'; + for (char &c : std::span(url->tag, strlen(url->tag))) { + if ((unsigned char)c < 0x20 || c == 0x7f) { + c = '?'; } } body_len = (size_t)(hash - body); diff --git a/tests/test_redir_qemu.sh b/tests/test_redir_qemu.sh index 947b2ec4..e9aab029 100755 --- a/tests/test_redir_qemu.sh +++ b/tests/test_redir_qemu.sh @@ -222,6 +222,7 @@ SERIAL_LOG="$WORK_DIR/serial.log" qemu-system-x86_64 \ $KVM_FLAG \ + -cpu max \ -m 512 \ -kernel "$WORK_DIR/vmlinuz" \ -initrd "$WORK_DIR/initramfs.gz" \