diff options
| author | Po Lu | 2023-05-05 19:04:32 +0800 |
|---|---|---|
| committer | Po Lu | 2023-05-05 19:04:32 +0800 |
| commit | 0fbe79727b07879cb4f0a5cb8d7288353c082bd0 (patch) | |
| tree | 921d949e0403488deba0ae13cc55ef736d095e23 /exec | |
| parent | 2ba6c5035c904426d564eac47381480158cbbb9e (diff) | |
| download | emacs-0fbe79727b07879cb4f0a5cb8d7288353c082bd0.tar.gz emacs-0fbe79727b07879cb4f0a5cb8d7288353c082bd0.zip | |
Fix execution of /proc/self/exe within child processes
* exec/exec.h (struct exec_tracee): New field `new_child'.
Also, make `waiting_for_syscall' a bitfield.
* exec/trace.c (PTRACE_GETEVENTMSG): New declaration.
(MAX_TRACEES): Bump to 4096.
(handle_clone_prepare): New function.
(handle_clone): If required, set `new_child' and wait for a
ptrace event describing the parent to arrive.
(after_fork): Clear new field.
(exec_waitpid): Upon a ptrace event describing a clone, create
the child's tracee if it doesn't already exist. Otherwise, copy
over the parent's cmdline and start running it.
Diffstat (limited to 'exec')
| -rw-r--r-- | exec/exec.h | 6 | ||||
| -rw-r--r-- | exec/trace.c | 187 |
2 files changed, 154 insertions, 39 deletions
diff --git a/exec/exec.h b/exec/exec.h index 625ad0bb219..8ee74d7ca8b 100644 --- a/exec/exec.h +++ b/exec/exec.h | |||
| @@ -153,7 +153,11 @@ struct exec_tracee | |||
| 153 | 153 | ||
| 154 | /* Whether or not the tracee is currently waiting for a system call | 154 | /* Whether or not the tracee is currently waiting for a system call |
| 155 | to complete. */ | 155 | to complete. */ |
| 156 | bool waiting_for_syscall; | 156 | bool waiting_for_syscall : 1; |
| 157 | |||
| 158 | /* Whether or not the tracee has been created but is not yet | ||
| 159 | processed by `handle_clone'. */ | ||
| 160 | bool new_child : 1; | ||
| 157 | 161 | ||
| 158 | #ifndef REENTRANT | 162 | #ifndef REENTRANT |
| 159 | /* Name of the executable being run. */ | 163 | /* Name of the executable being run. */ |
diff --git a/exec/trace.c b/exec/trace.c index b765b5cffa4..974df1dd5e1 100644 --- a/exec/trace.c +++ b/exec/trace.c | |||
| @@ -50,6 +50,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 50 | #define SYS_SECCOMP 1 | 50 | #define SYS_SECCOMP 1 |
| 51 | #endif /* SYS_SECCOMP */ | 51 | #endif /* SYS_SECCOMP */ |
| 52 | 52 | ||
| 53 | #ifndef PTRACE_GETEVENTMSG | ||
| 54 | #define PTRACE_GETEVENTMSG 0x4201 | ||
| 55 | #endif /* PTRACE_GETEVENTMSG */ | ||
| 56 | |||
| 53 | 57 | ||
| 54 | 58 | ||
| 55 | /* Program tracing functions. | 59 | /* Program tracing functions. |
| @@ -63,7 +67,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 63 | 67 | ||
| 64 | 68 | ||
| 65 | /* Number of tracees children are allowed to create. */ | 69 | /* Number of tracees children are allowed to create. */ |
| 66 | #define MAX_TRACEES 1024 | 70 | #define MAX_TRACEES 4096 |
| 67 | 71 | ||
| 68 | #ifdef __aarch64__ | 72 | #ifdef __aarch64__ |
| 69 | 73 | ||
| @@ -381,23 +385,66 @@ remove_tracee (struct exec_tracee *tracee) | |||
| 381 | 385 | ||
| 382 | /* Child process tracing. */ | 386 | /* Child process tracing. */ |
| 383 | 387 | ||
| 384 | /* Handle the completion of a `clone' or `clone3' system call, | 388 | /* Array of `struct exec_tracees' that they are allocated from. */ |
| 385 | resulting in the creation of the process PID. Allocate a new | 389 | static struct exec_tracee static_tracees[MAX_TRACEES]; |
| 386 | tracee structure from a static area for the processes's pid. | ||
| 387 | 390 | ||
| 388 | Value is 0 upon success, 1 otherwise. */ | 391 | /* Number of tracees currently allocated. */ |
| 392 | static int tracees; | ||
| 389 | 393 | ||
| 390 | static int | 394 | /* Return the `struct exec_tracee' corresponding to the specified |
| 391 | handle_clone (pid_t pid) | 395 | PROCESS. */ |
| 396 | |||
| 397 | static struct exec_tracee * | ||
| 398 | find_tracee (pid_t process) | ||
| 392 | { | 399 | { |
| 393 | static struct exec_tracee static_tracees[MAX_TRACEES]; | ||
| 394 | static int tracees; | ||
| 395 | struct exec_tracee *tracee; | 400 | struct exec_tracee *tracee; |
| 401 | |||
| 402 | for (tracee = tracing_processes; tracee; tracee = tracee->next) | ||
| 403 | { | ||
| 404 | if (tracee->pid == process) | ||
| 405 | return tracee; | ||
| 406 | } | ||
| 407 | |||
| 408 | return NULL; | ||
| 409 | } | ||
| 410 | |||
| 411 | /* Prepare to handle the completion of a `clone' system call. | ||
| 412 | |||
| 413 | If the new clone is not yet being traced, create a new tracee for | ||
| 414 | PARENT's child, copying over its current command line. Then, set | ||
| 415 | `new_child' in the new tracee. Otherwise, continue it until the | ||
| 416 | next syscall. */ | ||
| 417 | |||
| 418 | static void | ||
| 419 | handle_clone_prepare (struct exec_tracee *parent) | ||
| 420 | { | ||
| 421 | #ifndef REENTRANT | ||
| 396 | long rc; | 422 | long rc; |
| 397 | int flags; | 423 | unsigned long pid; |
| 424 | struct exec_tracee *tracee; | ||
| 398 | 425 | ||
| 399 | /* Now allocate a new tracee, either from static_tracees or the free | 426 | rc = ptrace (PTRACE_GETEVENTMSG, parent->pid, NULL, |
| 400 | list. */ | 427 | &pid); |
| 428 | if (rc) | ||
| 429 | return; | ||
| 430 | |||
| 431 | /* See if the tracee already exists. */ | ||
| 432 | tracee = find_tracee (pid); | ||
| 433 | |||
| 434 | if (tracee) | ||
| 435 | { | ||
| 436 | /* Continue the tracee. Record its command line, as that has | ||
| 437 | not yet been done. */ | ||
| 438 | |||
| 439 | assert (tracee->new_child); | ||
| 440 | tracee->new_child = false; | ||
| 441 | tracee->exec_file = NULL; | ||
| 442 | ptrace (PTRACE_SYSCALL, tracee->pid, 0, 0); | ||
| 443 | |||
| 444 | if (parent->exec_file) | ||
| 445 | tracee->exec_file = strdup (parent->exec_file); | ||
| 446 | return; | ||
| 447 | } | ||
| 401 | 448 | ||
| 402 | if (free_tracees) | 449 | if (free_tracees) |
| 403 | { | 450 | { |
| @@ -410,13 +457,75 @@ handle_clone (pid_t pid) | |||
| 410 | tracees++; | 457 | tracees++; |
| 411 | } | 458 | } |
| 412 | else | 459 | else |
| 413 | return 1; | 460 | return; |
| 414 | 461 | ||
| 415 | tracee->pid = pid; | 462 | tracee->pid = pid; |
| 416 | tracee->next = tracing_processes; | 463 | tracee->next = tracing_processes; |
| 417 | tracee->waiting_for_syscall = false; | 464 | tracee->waiting_for_syscall = false; |
| 465 | tracee->new_child = true; | ||
| 466 | tracee->exec_file = NULL; | ||
| 418 | tracing_processes = tracee; | 467 | tracing_processes = tracee; |
| 419 | 468 | ||
| 469 | /* Copy over the command line. */ | ||
| 470 | |||
| 471 | if (parent->exec_file) | ||
| 472 | tracee->exec_file = strdup (parent->exec_file); | ||
| 473 | #endif /* REENTRANT */ | ||
| 474 | } | ||
| 475 | |||
| 476 | /* Handle the completion of a `clone' or `clone3' system call, | ||
| 477 | resulting in the creation of the process PID. If TRACEE is NULL, | ||
| 478 | allocate a new tracee structure from a static area for the | ||
| 479 | processes's pid, then set TRACEE->new_child to true and await the | ||
| 480 | parent's corresponding ptrace event to arrive; otherwise, just | ||
| 481 | clear TRACEE->new_child. | ||
| 482 | |||
| 483 | Value is 0 upon success, 2 if TRACEE should remain suspended until | ||
| 484 | the parent's ptrace-stop, and 1 otherwise. */ | ||
| 485 | |||
| 486 | static int | ||
| 487 | handle_clone (struct exec_tracee *tracee, pid_t pid) | ||
| 488 | { | ||
| 489 | long rc; | ||
| 490 | int flags, value; | ||
| 491 | |||
| 492 | /* Now allocate a new tracee, either from static_tracees or the free | ||
| 493 | list, if no tracee was supplied. */ | ||
| 494 | |||
| 495 | value = 0; | ||
| 496 | |||
| 497 | if (!tracee) | ||
| 498 | { | ||
| 499 | if (free_tracees) | ||
| 500 | { | ||
| 501 | tracee = free_tracees; | ||
| 502 | free_tracees = free_tracees->next; | ||
| 503 | } | ||
| 504 | else if (tracees < MAX_TRACEES) | ||
| 505 | { | ||
| 506 | tracee = &static_tracees[tracees]; | ||
| 507 | tracees++; | ||
| 508 | } | ||
| 509 | else | ||
| 510 | return 1; | ||
| 511 | |||
| 512 | tracee->pid = pid; | ||
| 513 | tracee->next = tracing_processes; | ||
| 514 | tracee->waiting_for_syscall = false; | ||
| 515 | #ifndef REENTRANT | ||
| 516 | tracee->exec_file = NULL; | ||
| 517 | #endif /* REENTRANT */ | ||
| 518 | tracing_processes = tracee; | ||
| 519 | tracee->new_child = true; | ||
| 520 | |||
| 521 | /* Wait for the ptrace-stop to happen in the parent. */ | ||
| 522 | value = 2; | ||
| 523 | } | ||
| 524 | else | ||
| 525 | /* Clear the flag saying that this is a newly created child | ||
| 526 | process. */ | ||
| 527 | tracee->new_child = false; | ||
| 528 | |||
| 420 | /* Apply required options to the child, so that the kernel | 529 | /* Apply required options to the child, so that the kernel |
| 421 | automatically traces children and makes it easy to differentiate | 530 | automatically traces children and makes it easy to differentiate |
| 422 | between system call traps and other kinds of traps. */ | 531 | between system call traps and other kinds of traps. */ |
| @@ -432,15 +541,18 @@ handle_clone (pid_t pid) | |||
| 432 | if (rc) | 541 | if (rc) |
| 433 | goto bail; | 542 | goto bail; |
| 434 | 543 | ||
| 435 | /* The new tracee is currently stopped. Continue it until the next | 544 | if (value != 2) |
| 436 | system call. */ | 545 | { |
| 546 | /* The new tracee is currently stopped. Continue it until the next | ||
| 547 | system call. */ | ||
| 437 | 548 | ||
| 438 | rc = ptrace (PTRACE_SYSCALL, pid, 0, 0); | 549 | rc = ptrace (PTRACE_SYSCALL, pid, 0, 0); |
| 439 | 550 | ||
| 440 | if (rc) | 551 | if (rc) |
| 441 | goto bail; | 552 | goto bail; |
| 553 | } | ||
| 442 | 554 | ||
| 443 | return 0; | 555 | return value; |
| 444 | 556 | ||
| 445 | bail: | 557 | bail: |
| 446 | remove_tracee (tracee); | 558 | remove_tracee (tracee); |
| @@ -1148,6 +1260,7 @@ after_fork (pid_t pid) | |||
| 1148 | tracee->pid = pid; | 1260 | tracee->pid = pid; |
| 1149 | tracee->next = tracing_processes; | 1261 | tracee->next = tracing_processes; |
| 1150 | tracee->waiting_for_syscall = false; | 1262 | tracee->waiting_for_syscall = false; |
| 1263 | tracee->new_child = false; | ||
| 1151 | #ifndef REENTRANT | 1264 | #ifndef REENTRANT |
| 1152 | tracee->exec_file = NULL; | 1265 | tracee->exec_file = NULL; |
| 1153 | #endif /* REENTRANT */ | 1266 | #endif /* REENTRANT */ |
| @@ -1155,23 +1268,6 @@ after_fork (pid_t pid) | |||
| 1155 | return 0; | 1268 | return 0; |
| 1156 | } | 1269 | } |
| 1157 | 1270 | ||
| 1158 | /* Return the `struct exec_tracee' corresponding to the specified | ||
| 1159 | PROCESS. */ | ||
| 1160 | |||
| 1161 | static struct exec_tracee * | ||
| 1162 | find_tracee (pid_t process) | ||
| 1163 | { | ||
| 1164 | struct exec_tracee *tracee; | ||
| 1165 | |||
| 1166 | for (tracee = tracing_processes; tracee; tracee = tracee->next) | ||
| 1167 | { | ||
| 1168 | if (tracee->pid == process) | ||
| 1169 | return tracee; | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | return NULL; | ||
| 1173 | } | ||
| 1174 | |||
| 1175 | /* Wait for a child process to exit, like `waitpid'. However, if a | 1271 | /* Wait for a child process to exit, like `waitpid'. However, if a |
| 1176 | child stops to perform a system call, send it on its way and return | 1272 | child stops to perform a system call, send it on its way and return |
| 1177 | -1. OPTIONS must not contain WUNTRACED. */ | 1273 | -1. OPTIONS must not contain WUNTRACED. */ |
| @@ -1199,12 +1295,12 @@ exec_waitpid (pid_t pid, int *wstatus, int options) | |||
| 1199 | { | 1295 | { |
| 1200 | tracee = find_tracee (pid); | 1296 | tracee = find_tracee (pid); |
| 1201 | 1297 | ||
| 1202 | if (!tracee) | 1298 | if (!tracee || tracee->new_child) |
| 1203 | { | 1299 | { |
| 1204 | if (WSTOPSIG (status) == SIGSTOP) | 1300 | if (WSTOPSIG (status) == SIGSTOP) |
| 1205 | /* A new process has been created and stopped. Record | 1301 | /* A new process has been created and stopped. Record |
| 1206 | it now. */ | 1302 | it now. */ |
| 1207 | handle_clone (pid); | 1303 | handle_clone (tracee, pid); |
| 1208 | 1304 | ||
| 1209 | return -1; | 1305 | return -1; |
| 1210 | } | 1306 | } |
| @@ -1248,6 +1344,21 @@ exec_waitpid (pid_t pid, int *wstatus, int options) | |||
| 1248 | case SIGTRAP | (PTRACE_EVENT_FORK << 8): | 1344 | case SIGTRAP | (PTRACE_EVENT_FORK << 8): |
| 1249 | case SIGTRAP | (PTRACE_EVENT_VFORK << 8): | 1345 | case SIGTRAP | (PTRACE_EVENT_VFORK << 8): |
| 1250 | case SIGTRAP | (PTRACE_EVENT_CLONE << 8): | 1346 | case SIGTRAP | (PTRACE_EVENT_CLONE << 8): |
| 1347 | |||
| 1348 | /* Both PTRACE_EVENT_CLONE and SIGSTOP must arrive before a | ||
| 1349 | process is continued. Otherwise, its parent's cmdline | ||
| 1350 | cannot be obtained and propagated. | ||
| 1351 | |||
| 1352 | If the PID of the new process is currently not being | ||
| 1353 | traced, create a new tracee. Set `new_child' to true, | ||
| 1354 | and copy over the old command line in preparation for a | ||
| 1355 | SIGSTOP signal being delivered to it. | ||
| 1356 | |||
| 1357 | Otherwise, start the tracee running until the next | ||
| 1358 | syscall. */ | ||
| 1359 | |||
| 1360 | handle_clone_prepare (tracee); | ||
| 1361 | |||
| 1251 | /* These events are handled by tracing SIGSTOP signals sent | 1362 | /* These events are handled by tracing SIGSTOP signals sent |
| 1252 | to unknown tracees. Make sure not to pass through | 1363 | to unknown tracees. Make sure not to pass through |
| 1253 | status, as there's no signal really being delivered. */ | 1364 | status, as there's no signal really being delivered. */ |