crash/0002-Fix-incorrect-task-state-during-exit.patch
Tao Liu 90c3b10352 Sync to upstream commit 7e8a2796580d
Release: crash-9.0.0-2

Resolves: RHEL-89990

Signed-off-by: Tao Liu <ltao@redhat.com>
2025-07-02 11:29:37 +12:00

84 lines
3.4 KiB
Diff

From 6eb51d8284aaca9cc882ddb1b9e135c708abbaa4 Mon Sep 17 00:00:00 2001
From: Stephen Brennan <stephen.s.brennan@oracle.com>
Date: Fri, 2 May 2025 13:18:17 -0700
Subject: [PATCH 2/9] Fix incorrect task state during exit
task_state() assumes that exit_state is a unsigned long, when in
reality, it has been declared as an int since 97dc32cdb1b53 ("reduce
size of task_struct on 64-bit machines"), in Linux 2.6.22. So on 64-bit
machines, task_state() reads 8 bytes rather than 4, and gets the wrong
exit_state value by including the next field.
This has gone unnoticed because directly after exit_state comes
exit_code, which is generally zero while the task is alive. When the
exit_code is set, exit_state is usually set not long after. Since
task_state_string() only checks whether exit_state bits are set, it
never notices the presence of the exit code inside of the state.
But this leaves open a window during the process exit, when the
exit_code has been set (in do_exit()), but the exit_state has not (in
exit_notify()). In this case, crash reports a state of "??", but in
reality, the task is still running -- it's just running the exit()
system call. This race window can be long enough to be observed in core
dumps, for example if the mmput() takes a long time.
This should be considered a bug. A task state of "??" or "(unknown)" is
frequently of concern when debugging, as it could indicate that the
state fields had some sort of corruption, and draw the attention of the
debugger. To handle it properly, record the size of exit_state, and read
it conditionally as a UINT or ULONG, just like the state. This ensures
we retain compatibility with kernel before v2.6.22. Whether that is
actually desirable is anybody's guess.
Reported-by: Jeffery Yoder <jeffery.yoder@oracle.com>
Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
---
defs.h | 1 +
task.c | 11 +++++++++--
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/defs.h b/defs.h
index 4cf169c..2fdb4db 100644
--- a/defs.h
+++ b/defs.h
@@ -2448,6 +2448,7 @@ struct size_table { /* stash of commonly-used sizes */
long fred_frame;
long vmap_node;
long cpumask_t;
+ long task_struct_exit_state;
};
struct array_table {
diff --git a/task.c b/task.c
index 3bafe79..e07b479 100644
--- a/task.c
+++ b/task.c
@@ -306,6 +306,7 @@ task_init(void)
MEMBER_SIZE_INIT(task_struct_state, "task_struct", "__state");
}
MEMBER_OFFSET_INIT(task_struct_exit_state, "task_struct", "exit_state");
+ MEMBER_SIZE_INIT(task_struct_exit_state, "task_struct", "exit_state");
MEMBER_OFFSET_INIT(task_struct_pid, "task_struct", "pid");
MEMBER_OFFSET_INIT(task_struct_comm, "task_struct", "comm");
MEMBER_OFFSET_INIT(task_struct_next_task, "task_struct", "next_task");
@@ -5965,8 +5966,14 @@ task_state(ulong task)
state = ULONG(tt->task_struct + OFFSET(task_struct_state));
else
state = UINT(tt->task_struct + OFFSET(task_struct_state));
- exit_state = VALID_MEMBER(task_struct_exit_state) ?
- ULONG(tt->task_struct + OFFSET(task_struct_exit_state)) : 0;
+
+ if (VALID_MEMBER(task_struct_exit_state)
+ && SIZE(task_struct_exit_state) == sizeof(ulong))
+ exit_state = ULONG(tt->task_struct + OFFSET(task_struct_exit_state));
+ else if (VALID_MEMBER(task_struct_exit_state))
+ exit_state = UINT(tt->task_struct + OFFSET(task_struct_exit_state));
+ else
+ exit_state = 0;
return (state | exit_state);
}
--
2.47.0