-
Notifications
You must be signed in to change notification settings - Fork 14.1k
[lldb] Fix SIGSEGV in GetPtraceScope()
in Procfs.cpp
#142224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-lldb Author: None (royitaqi) ChangesSymptomWe have seen SIGSEGV like this:
See full stack trace. A similar error (an unchecked Root cause
The intention of the Note that the method FixThe fix is simply remove the TestAdding a new unit test: On a Linux machine which doesn't have
Run the unit test with the fix show passed result:
Full diff: https://github.com/llvm/llvm-project/pull/142224.diff 2 Files Affected:
diff --git a/lldb/source/Plugins/Process/Linux/Procfs.cpp b/lldb/source/Plugins/Process/Linux/Procfs.cpp
index d3bd396fbaeab..4349a2b1adb9d 100644
--- a/lldb/source/Plugins/Process/Linux/Procfs.cpp
+++ b/lldb/source/Plugins/Process/Linux/Procfs.cpp
@@ -74,7 +74,7 @@ lldb_private::process_linux::GetAvailableLogicalCoreIDs() {
llvm::Expected<int> lldb_private::process_linux::GetPtraceScope() {
ErrorOr<std::unique_ptr<MemoryBuffer>> ptrace_scope_file =
getProcFile("sys/kernel/yama/ptrace_scope");
- if (!*ptrace_scope_file)
+ if (!ptrace_scope_file)
return errorCodeToError(ptrace_scope_file.getError());
// The contents should be something like "1\n". Trim it so we get "1".
StringRef buffer = (*ptrace_scope_file)->getBuffer().trim();
diff --git a/lldb/unittests/Process/Linux/ProcfsTests.cpp b/lldb/unittests/Process/Linux/ProcfsTests.cpp
index e7af1f469c2bf..a795fa4e019c5 100644
--- a/lldb/unittests/Process/Linux/ProcfsTests.cpp
+++ b/lldb/unittests/Process/Linux/ProcfsTests.cpp
@@ -104,7 +104,7 @@ TEST(Perf, RealLogicalCoreIDs) {
ASSERT_GT((int)cpu_ids->size(), 0) << "We must see at least one core";
}
-TEST(Perf, RealPtraceScope) {
+TEST(Perf, RealPtraceScopeWhenExist) {
// We first check we can read /proc/sys/kernel/yama/ptrace_scope
auto buffer_or_error =
errorOrToExpected(getProcFile("sys/kernel/yama/ptrace_scope"));
@@ -120,6 +120,20 @@ TEST(Perf, RealPtraceScope) {
<< "Sensible values of ptrace_scope are between 0 and 3";
}
+TEST(Perf, RealPtraceScopeWhenNotExist) {
+ // We first check we can NOT read /proc/sys/kernel/yama/ptrace_scope
+ auto buffer_or_error =
+ errorOrToExpected(getProcFile("sys/kernel/yama/ptrace_scope"));
+ if (buffer_or_error)
+ GTEST_SKIP() << "In order for this test to run, /proc/sys/kernel/yama/ptrace_scope should not exist";
+ consumeError(buffer_or_error.takeError());
+
+ // At this point we should fail parsing the ptrace_scope value.
+ Expected<int> ptrace_scope = GetPtraceScope();
+ ASSERT_FALSE((bool)ptrace_scope);
+ consumeError(ptrace_scope.takeError());
+}
+
#ifdef LLVM_ENABLE_THREADING
TEST(Support, getProcFile_Tid) {
auto BufferOrError = getProcFile(getpid(), llvm::get_threadid(), "comm");
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
@@ -74,7 +74,7 @@ lldb_private::process_linux::GetAvailableLogicalCoreIDs() { | |||
llvm::Expected<int> lldb_private::process_linux::GetPtraceScope() { | |||
ErrorOr<std::unique_ptr<MemoryBuffer>> ptrace_scope_file = | |||
getProcFile("sys/kernel/yama/ptrace_scope"); | |||
if (!*ptrace_scope_file) | |||
if (!ptrace_scope_file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to do both, !ptrace_scope_file || !*ptrace_scope_file
.
The first checks the Error, the second the unique_ptr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 2nd one has to be in a different if
statement, because ptrace_scope_file.getError()
would have no error.
Adding this in the latest commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we do. The default for functions returning Expected/ErrorOr<some_ptr<Foo>>
is that they return a valid object in the non-error case. If they aren't they should very explicitly document what it means to "successfully return nothing"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing the newly added if
per @labath 's comment.
@@ -104,7 +104,7 @@ TEST(Perf, RealLogicalCoreIDs) { | |||
ASSERT_GT((int)cpu_ids->size(), 0) << "We must see at least one core"; | |||
} | |||
|
|||
TEST(Perf, RealPtraceScope) { | |||
TEST(Perf, RealPtraceScopeWhenExist) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will probably fail, I don't think kernel/yama
is guaranteed to exist on ALL Linux hosts. We should either create it or not.
I think this original code assumed if you ever got an EPERM from ATTACH you would always have this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will probably fail
I don't think so. Look at line 112. It skips the test if the file doesn't exist.
We should either create it or not.
I am not sure if we want a unit test to create files that may or may not be depended on by other tests, etc.
Given how the original code was written (hardcoded path), the current setup (skip one test or the other) seems to be reasonable. I agree it's not ideal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @rupprecht (who added the original unit test) in case they have additional thoughts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a bug that I wrote if (!*ptrace_scope_file)
instead of if (!ptrace_scope_file)
-- I don't think there was anything I had in mind when writing it that way, e.g. if we should be able to assume the ptrace scope file exists & is readable. I'm surprised nobody noticed this bug earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@@ -74,7 +74,7 @@ lldb_private::process_linux::GetAvailableLogicalCoreIDs() { | |||
llvm::Expected<int> lldb_private::process_linux::GetPtraceScope() { | |||
ErrorOr<std::unique_ptr<MemoryBuffer>> ptrace_scope_file = | |||
getProcFile("sys/kernel/yama/ptrace_scope"); | |||
if (!*ptrace_scope_file) | |||
if (!ptrace_scope_file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we do. The default for functions returning Expected/ErrorOr<some_ptr<Foo>>
is that they return a valid object in the non-error case. If they aren't they should very explicitly document what it means to "successfully return nothing"
Updated according to @labath 's comment. @JDevlieghere / @clayborg: Gentle ping - Do you guys want to review, or should I go ahead and land with @labath 's approval? |
if (buffer_or_error) | ||
GTEST_SKIP() << "In order for this test to run, " | ||
"/proc/sys/kernel/yama/ptrace_scope should not exist"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be early returning here if /proc/sys/kernel/yama/ptrace_scope
doesn't exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does GTEST_SKIP cause an early return? And if the file does exist, do we want to create noise in the gtest output? We just want to make sure that if the file doesn't exist, we don't crash right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like GTEST_SKIP does do an early return somehow after checking the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
@@ -104,7 +104,7 @@ TEST(Perf, RealLogicalCoreIDs) { | |||
ASSERT_GT((int)cpu_ids->size(), 0) << "We must see at least one core"; | |||
} | |||
|
|||
TEST(Perf, RealPtraceScope) { | |||
TEST(Perf, RealPtraceScopeWhenExist) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a bug that I wrote if (!*ptrace_scope_file)
instead of if (!ptrace_scope_file)
-- I don't think there was anything I had in mind when writing it that way, e.g. if we should be able to assume the ptrace scope file exists & is readable. I'm surprised nobody noticed this bug earlier.
# Symptom We have seen SIGSEGV like this: ``` * thread llvm#1, name = 'lldb-server', stop reason = SIGSEGV frame #0: 0x00007f39e529c993 libc.so.6`__pthread_kill_internal(signo=11, threadid=<unavailable>) at pthread_kill.c:46:37 ... * frame llvm#5: 0x000056027c94fe48 lldb-server`lldb_private::process_linux::GetPtraceScope() + 72 frame llvm#6: 0x000056027c92f94f lldb-server`lldb_private::process_linux::NativeProcessLinux::Attach(int) + 1087 ... ``` See [full stack trace](https://pastebin.com/X0d6QhYj). This happens on Linux where LLDB doesn't have access to `/proc/sys/kernel/yama/ptrace_scope`. A similar error (an unchecked `Error`) can be reproduced by running the newly added unit test without the fix. See the "Test" section below. # Root cause `GetPtraceScope()` ([code](https://github.com/llvm/llvm-project/blob/328f40f408c218f25695ea42c844e43bef38660b/lldb/source/Plugins/Process/Linux/Procfs.cpp#L77)) has the following `if` statement: ``` llvm::Expected<int> lldb_private::process_linux::GetPtraceScope() { ErrorOr<std::unique_ptr<MemoryBuffer>> ptrace_scope_file = getProcFile("sys/kernel/yama/ptrace_scope"); if (!*ptrace_scope_file) return errorCodeToError(ptrace_scope_file.getError()); ... } ``` The intention of the `if` statement is to check whether the `ptrace_scope_file` is an `Error` or not, and return the error if it is. However, the `operator*` of `ErrorOr` returns the value that is stored (which is a `std::unique_ptr<MemoryBuffer>`), so what the `if` condition actually do is to check if the unique pointer is non-null. Note that the method `ErrorOr::getStorage()` ([called by](https://github.com/llvm/llvm-project/blob/328f40f408c218f25695ea42c844e43bef38660b/llvm/include/llvm/Support/ErrorOr.h#L162-L164) `ErrorOr::operator *`) **does** assert on whether or not `HasError` has been set (see [ErrorOr.h](https://github.com/llvm/llvm-project/blob/328f40f408c218f25695ea42c844e43bef38660b/llvm/include/llvm/Support/ErrorOr.h#L235-L243)). However, it seems this wasn't executed, probably because the LLDB was a release build. # Fix The fix is simply remove the `*` in the said `if` statement.
# Symptom We have seen SIGSEGV like this: ``` * thread #1, name = 'lldb-server', stop reason = SIGSEGV frame #0: 0x00007f39e529c993 libc.so.6`__pthread_kill_internal(signo=11, threadid=<unavailable>) at pthread_kill.c:46:37 ... * frame #5: 0x000056027c94fe48 lldb-server`lldb_private::process_linux::GetPtraceScope() + 72 frame #6: 0x000056027c92f94f lldb-server`lldb_private::process_linux::NativeProcessLinux::Attach(int) + 1087 ... ``` See [full stack trace](https://pastebin.com/X0d6QhYj). This happens on Linux where LLDB doesn't have access to `/proc/sys/kernel/yama/ptrace_scope`. A similar error (an unchecked `Error`) can be reproduced by running the newly added unit test without the fix. See the "Test" section below. # Root cause `GetPtraceScope()` ([code](https://github.com/llvm/llvm-project/blob/328f40f408c218f25695ea42c844e43bef38660b/lldb/source/Plugins/Process/Linux/Procfs.cpp#L77)) has the following `if` statement: ``` llvm::Expected<int> lldb_private::process_linux::GetPtraceScope() { ErrorOr<std::unique_ptr<MemoryBuffer>> ptrace_scope_file = getProcFile("sys/kernel/yama/ptrace_scope"); if (!*ptrace_scope_file) return errorCodeToError(ptrace_scope_file.getError()); ... } ``` The intention of the `if` statement is to check whether the `ptrace_scope_file` is an `Error` or not, and return the error if it is. However, the `operator*` of `ErrorOr` returns the value that is stored (which is a `std::unique_ptr<MemoryBuffer>`), so what the `if` condition actually do is to check if the unique pointer is non-null. Note that the method `ErrorOr::getStorage()` ([called by](https://github.com/llvm/llvm-project/blob/328f40f408c218f25695ea42c844e43bef38660b/llvm/include/llvm/Support/ErrorOr.h#L162-L164) `ErrorOr::operator *`) **does** assert on whether or not `HasError` has been set (see [ErrorOr.h](https://github.com/llvm/llvm-project/blob/328f40f408c218f25695ea42c844e43bef38660b/llvm/include/llvm/Support/ErrorOr.h#L235-L243)). However, it seems this wasn't executed, probably because the LLDB was a release build. # Fix The fix is simply remove the `*` in the said `if` statement.
Symptom
We have seen SIGSEGV like this:
See full stack trace.
This happens on Linux where LLDB doesn't have access to
/proc/sys/kernel/yama/ptrace_scope
.A similar error (an unchecked
Error
) can be reproduced by running the newly added unit test without the fix. See the "Test" section below.Root cause
GetPtraceScope()
(code) has the followingif
statement:The intention of the
if
statement is to check whether theptrace_scope_file
is anError
or not, and return the error if it is. However, theoperator*
ofErrorOr
returns the value that is stored (which is astd::unique_ptr<MemoryBuffer>
), so what theif
condition actually do is to check if the unique pointer is non-null.Note that the method
ErrorOr::getStorage()
(called byErrorOr::operator *
) does assert on whether or notHasError
has been set (see ErrorOr.h). However, it seems this wasn't executed, probably because the LLDB was a release build.Fix
The fix is simply remove the
*
in the saidif
statement.Test
Adding a new unit test:
RealPtraceScopeWhenNotExist
.On a Linux machine which doesn't have
/proc/sys/kernel/yama/ptrace_scope
, run the unit test without the fix reproduces this assertion error about an uncheckedError
:Run the unit test with the fix show passed result: