Skip to content

Commit 11bbf0d

Browse files
sunmingqiangDeusData
authored andcommitted
fix(mcp): survive atomic binary replacement
Prefer the saved executable launch path when it still points to an executable, and reject a deleted /proc/self/exe path before spawning index workers. Add a Linux integration regression that forks, atomically replaces the running test binary on disk, and verifies that subprocess resolution uses the saved launch path instead of the now-deleted self path. Signed-off-by: David Musk <davidmusk93@users.noreply.github.com> Signed-off-by: sunmingqiang <sunmingqiang@bytedance.com>
1 parent e65ace1 commit 11bbf0d

3 files changed

Lines changed: 97 additions & 0 deletions

File tree

‎src/ui/http_server.c‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,9 +1052,18 @@ bool cbm_http_server_resolve_binary_path(const char *argv0, char *out, size_t ou
10521052
}
10531053
#endif
10541054

1055+
#ifndef _WIN32
1056+
if (g_binary_path[0] && is_executable_file(g_binary_path)) {
1057+
return copy_path(out, outsz, g_binary_path);
1058+
}
1059+
if (resolve_self_executable(out, outsz) && is_executable_file(out)) {
1060+
return true;
1061+
}
1062+
#else
10551063
if (resolve_self_executable(out, outsz)) {
10561064
return true;
10571065
}
1066+
#endif
10581067
return copy_path(out, outsz, argv0);
10591068
}
10601069

‎tests/test_httpd.c‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,69 @@ TEST(httpd_resolves_bare_binary_path_from_path) {
457457
#endif
458458
}
459459

460+
TEST(httpd_resolves_deleted_self_to_replaced_launch_path) {
461+
#if defined(__linux__)
462+
char tmpdir[256];
463+
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cbm_httpd_deleted_self_XXXXXX");
464+
ASSERT_NOT_NULL(cbm_mkdtemp(tmpdir));
465+
466+
char current[1024];
467+
ssize_t current_len = readlink("/proc/self/exe", current, sizeof(current) - 1);
468+
ASSERT_GT(current_len, 0);
469+
ASSERT_LT(current_len, (ssize_t)(sizeof(current) - 1));
470+
current[current_len] = '\0';
471+
472+
char launch_path[512];
473+
char replacement_path[512];
474+
snprintf(launch_path, sizeof(launch_path), "%s/test-runner", tmpdir);
475+
snprintf(replacement_path, sizeof(replacement_path), "%s/test-runner.new", tmpdir);
476+
ASSERT_EQ(cbm_copy_file(current, launch_path), 0);
477+
ASSERT_EQ(chmod(launch_path, 0755), 0);
478+
479+
int ready_pipe[2];
480+
int continue_pipe[2];
481+
ASSERT_EQ(pipe(ready_pipe), 0);
482+
ASSERT_EQ(pipe(continue_pipe), 0);
483+
484+
pid_t child = fork();
485+
if (child == 0) {
486+
close(ready_pipe[0]);
487+
close(continue_pipe[1]);
488+
char ready_fd[32];
489+
char continue_fd[32];
490+
snprintf(ready_fd, sizeof(ready_fd), "%d", ready_pipe[1]);
491+
snprintf(continue_fd, sizeof(continue_fd), "%d", continue_pipe[0]);
492+
execl(launch_path, launch_path, "__cbm_deleted_self_probe", ready_fd, continue_fd,
493+
launch_path, (char *)NULL);
494+
_exit(127);
495+
}
496+
ASSERT_GT(child, 0);
497+
498+
close(ready_pipe[1]);
499+
close(continue_pipe[0]);
500+
char ready = '\0';
501+
ASSERT_EQ(read(ready_pipe[0], &ready, 1), 1);
502+
ASSERT_EQ(ready, 'R');
503+
504+
ASSERT_EQ(cbm_copy_file(current, replacement_path), 0);
505+
ASSERT_EQ(chmod(replacement_path, 0755), 0);
506+
ASSERT_EQ(rename(replacement_path, launch_path), 0);
507+
ASSERT_EQ(write(continue_pipe[1], "G", 1), 1);
508+
close(ready_pipe[0]);
509+
close(continue_pipe[1]);
510+
511+
int status = 0;
512+
ASSERT_EQ(waitpid(child, &status, 0), child);
513+
ASSERT_TRUE(WIFEXITED(status));
514+
ASSERT_EQ(WEXITSTATUS(status), 0);
515+
unlink(launch_path);
516+
rmdir(tmpdir);
517+
PASS();
518+
#else
519+
PASS();
520+
#endif
521+
}
522+
460523
/* ── Transport integration (listener only) ────────────────────── */
461524

462525
TEST(httpd_listen_ephemeral_port) {
@@ -2278,6 +2341,7 @@ SUITE(httpd) {
22782341
RUN_TEST(httpd_query_param_edge_cases);
22792342
RUN_TEST(httpd_path_match_matrix);
22802343
RUN_TEST(httpd_resolves_bare_binary_path_from_path);
2344+
RUN_TEST(httpd_resolves_deleted_self_to_replaced_launch_path);
22812345
RUN_TEST(repo_info_web_base_normalizes_to_https);
22822346
RUN_TEST(repo_info_strips_credentials_from_remote);
22832347

‎tests/test_main.c‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ int tf_skip_count = 0;
2222
#include "daemon/version_cohort.h" /* Windows crash-turnover re-exec probe */
2323
#include "mcp/index_supervisor.h" /* cbm_index_set_worker_role */
2424
#include "mcp/mcp.h" /* cbm_mcp_handle_tool — act as a real worker */
25+
#include "ui/http_server.h" /* deleted-self executable probe */
2526
#include <sqlite3.h>
2627
#include <errno.h>
2728
#include <stdbool.h>
@@ -608,6 +609,26 @@ static int tf_maybe_run_mcp_idxfailclosed_probe(int argc, char **argv) {
608609
const char *cache_dir);
609610
alarm(20);
610611
return mcp_test_idxfailclosed_supervisor_start_check(argv[2], argv[3]);
612+
static int tf_maybe_run_deleted_self_probe(int argc, char **argv) {
613+
#if defined(__linux__)
614+
if (argc != 5 || strcmp(argv[1], "__cbm_deleted_self_probe") != 0) {
615+
return -1;
616+
}
617+
int ready_fd = atoi(argv[2]);
618+
int continue_fd = atoi(argv[3]);
619+
cbm_http_server_set_binary_path(argv[4]);
620+
if (write(ready_fd, "R", 1) != 1) {
621+
return 41;
622+
}
623+
char go = '\0';
624+
if (read(continue_fd, &go, 1) != 1) {
625+
return 42;
626+
}
627+
char resolved[1024];
628+
if (!cbm_http_server_resolve_binary_path(NULL, resolved, sizeof(resolved))) {
629+
return 43;
630+
}
631+
return strcmp(resolved, argv[4]) == 0 && access(resolved, X_OK) == 0 ? 0 : 44;
611632
#else
612633
(void)argc;
613634
(void)argv;
@@ -866,6 +887,9 @@ int main(int argc, char **argv) {
866887
int daemon_ipc_probe_rc = tf_maybe_run_daemon_ipc_lock_probe(argc, argv);
867888
if (daemon_ipc_probe_rc >= 0) {
868889
return daemon_ipc_probe_rc;
890+
int deleted_self_rc = tf_maybe_run_deleted_self_probe(argc, argv);
891+
if (deleted_self_rc >= 0) {
892+
return deleted_self_rc;
869893
}
870894

871895
/* #798 follow-up: if spawned as the socket-isolation probe, report whether an

0 commit comments

Comments
 (0)