ptrace: make ptrace() fail if the tracee changed its pid unexpectedly
Subsystems (2)
Sasha Levin
maintainerPATCH 1/2 ptrace: make ptrace() fail if the tracee changed its pid unexpectedly
From: Oleg Nesterov <[email protected]>
[ Upstream commit dbb5afad100a828c97e012c6106566d99f041db6 ]
Suppose we have 2 threads, the group-leader L and a sub-theread T, both parked in ptrace_stop(). Debugger tries to resume both threads and does
ptrace(PTRACE_CONT, T); ptrace(PTRACE_CONT, L);
If the sub-thread T execs in between, the 2nd PTRACE_CONT doesn not resume the old leader L, it resumes the post-exec thread T which was actually now stopped in PTHREAD_EVENT_EXEC. In this case the PTHREAD_EVENT_EXEC event is lost, and the tracer can't know that the tracee changed its pid.
This patch makes ptrace() fail in this case until debugger does wait() and consumes PTHREAD_EVENT_EXEC which reports old_pid. This affects all ptrace requests except the "asynchronous" PTRACE_INTERRUPT/KILL.
The patch doesn't add the new PTRACE_ option to not complicate the API, and I _hope_ this won't cause any noticeable regression:
- If debugger uses PTRACE_O_TRACEEXEC and the thread did an exec and the tracer does a ptrace request without having consumed the exec event, it's 100% sure that the thread the ptracer thinks it is targeting does not exist anymore, or isn't the same as the one it thinks it is targeting.
- To some degree this patch adds nothing new. In the scenario above ptrace(L) can fail with -ESRCH if it is called after the execing sub-thread wakes the leader up and before it "steals" the leader's pid.
Test-case:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
#include <assert.h>
void *tf(void *arg)
{
execve("/usr/bin/true", NULL, NULL);
assert(0);
return NULL;
}
int main(void)
{
int leader = fork();
if (!leader) {
kill(getpid(), SIGSTOP);
pthread_t th;
pthread_create(&th, NULL, tf, NULL);
for (;;)
pause();
return 0;
}
waitpid(leader, NULL, WSTOPPED);
ptrace(PTRACE_SEIZE, leader, 0,
PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC);
waitpid(leader, NULL, 0);
ptrace(PTRACE_CONT, leader, 0,0);
waitpid(leader, NULL, 0);
int status, thread = waitpid(-1, &status, 0);
assert(thread > 0 && thread != leader);
assert(status == 0x80137f);
ptrace(PTRACE_CONT, thread, 0,0);
/*
* waitid() because waitpid(leader, &status, WNOWAIT) does not
* report status. Why ????
*
* Why WEXITED? because we have another kernel problem connected
* to mt-exec.
*/
siginfo_t info;
assert(waitid(P_PID, leader, &info, WSTOPPED|WEXITED|WNOWAIT) == 0);
assert(info.si_pid == leader && info.si_status == 0x0405);
/* OK, it sleeps in ptrace(PTRACE_EVENT_EXEC == 0x04) */
assert(ptrace(PTRACE_CONT, leader, 0,0) == -1);
assert(errno == ESRCH);
assert(leader == waitpid(leader, &status, WNOHANG));
assert(status == 0x04057f);
assert(ptrace(PTRACE_CONT, leader, 0,0) == 0);
return 0;
}--- kernel/ptrace.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-)
kernel/ptrace.c +17 -1
@@ -163,6 +163,21 @@ void __ptrace_unlink(struct task_struct *child)spin_unlock (&child->sighand->siglock); } +static bool looks_like_a_spurious_pid (struct task_struct *task) +{ +if (task->exit_code != ((PTRACE_EVENT_EXEC <<8 ) |SIGTRAP )) +return false; + +if (task_pid_vnr (task) == task->ptrace_message) +return false; +/* +* The tracee changed its pid but the PTRACE_EVENT_EXEC event +* was not wait()'ed, most probably debugger targets the old +* leader which was destroyed in de_thread(). +*/ +return true; +} +/* Ensure that nothing can wake it up, even SIGKILL */ static bool ptrace_freeze_traced (struct task_struct *task) {
@@ -173,7 +188,8 @@ static bool ptrace_freeze_traced(struct task_struct *task)return ret;spin_lock_irq (&task->sighand->siglock); -if (task_is_traced (task) && !__fatal_signal_pending (task)) { +if (task_is_traced (task) && !looks_like_a_spurious_pid (task) && + !__fatal_signal_pending (task)) { task->state = __TASK_TRACED; ret = true; }
Signature · 1 line
2.30.2
Sasha Levin
maintainerPATCH 2/2 nvmet: seset ns->file when open fails
From: Daniel Wagner <[email protected]>
[ Upstream commit 85428beac80dbcace5b146b218697c73e367dcf5 ]
Reset the ns->file value to NULL also in the error case in nvmet_file_ns_enable().
The ns->file variable points either to file object or contains the error code after the filp_open() call. This can lead to following problem:
When the user first setups an invalid file backend and tries to enable the ns, it will fail. Then the user switches over to a bdev backend and enables successfully the ns. The first received I/O will crash the system because the IO backend is chosen based on the ns->file value:
static u16 nvmet_parse_io_cmd(struct nvmet_req *req)
{
[...]
if (req->ns->file)
return nvmet_file_parse_io_cmd(req);
return nvmet_bdev_parse_io_cmd(req);
}--- drivers/nvme/target/io-cmd-file.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-)
drivers/nvme/target/io-cmd-file.c +5 -3
@@ -38,9 +38,11 @@ int nvmet_file_ns_enable(struct nvmet_ns *ns) ns->file =filp_open (ns->device_path, flags,0 );if (IS_ERR (ns->file)) { -pr_err ("failed to open file %s: (%ld)\n" , - ns->device_path,PTR_ERR (ns->file)); -return PTR_ERR (ns->file); + ret =PTR_ERR (ns->file); +pr_err ("failed to open file %s: (%d)\n" , + ns->device_path, ret); + ns->file =NULL ; +return ret; } ret = vfs_getattr(&ns->file->f_path,
Signature · 1 line
2.30.2