Skip to content

Commit

Permalink
Harden --locals against trashed memory
Browse files Browse the repository at this point in the history
When attempting to produce a repr for local variables, attempt to cope
better with trashed memory: if the object's `ob_type` pointer is
invalid, use "<invalid typeobject at 0x$ADDRESS>" as its class name. If we
fail to read any other field needed for stringifying an object, generate
"<$CLASSNAME object at 0x$ADDRESS>" for its repr.

Test this with some ctypes abuse.

Signed-off-by: Matt Wozniski <[email protected]>
  • Loading branch information
godlygeek committed Jul 31, 2024
1 parent c0b8b49 commit 0c7a734
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 19 deletions.
60 changes: 41 additions & 19 deletions src/pystack/_pystack/pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,9 +531,22 @@ Object::Object(const std::shared_ptr<const AbstractProcessManager>& manager, rem
PyTypeObject cls;
LOG(DEBUG) << std::hex << std::showbase << "Copying typeobject from address " << obj.ob_type;
d_type_addr = reinterpret_cast<remote_addr_t>(obj.ob_type);
manager->copyMemoryFromProcess((remote_addr_t)obj.ob_type, manager->offsets().py_type.size, &cls);
try {
manager->copyMemoryFromProcess(
(remote_addr_t)obj.ob_type,
manager->offsets().py_type.size,
&cls);

d_flags = manager->getField(cls, &py_type_v::o_tp_flags);
d_flags = manager->getField(cls, &py_type_v::o_tp_flags);
} catch (RemoteMemCopyError& ex) {
LOG(WARNING) << std::hex << std::showbase << "Failed to read typeobject from address "
<< obj.ob_type;
std::stringstream os;
os << std::hex << std::showbase << "<invalid typeobject at " << obj.ob_type << ">";
d_classname = os.str();
d_flags = 0;
return;
}

remote_addr_t name_addr = manager->getField(cls, &py_type_v::o_tp_name);
try {
Expand Down Expand Up @@ -577,24 +590,33 @@ Object::toString(ssize_t max_size) const
if (max_size <= 5) {
return ELLIPSIS;
}
std::stringstream os;
std::visit(
overloaded{
[&](const auto& arg) { os << arg.toString(max_size); },
[&](const bool arg) { os << arg; },
[&](const long arg) { os << arg; },
[&](const double arg) { os << arg; },
[&](const std::string& arg) {
std::string truncated = arg;
if (static_cast<size_t>(max_size) < arg.size()) {
truncated = arg.substr(0, max_size - 3) + "...";
}
os << truncated;
},
},
toConcreteObject());
try {
std::stringstream os;
std::visit(
overloaded{
[&](const auto& arg) { os << arg.toString(max_size); },
[&](const bool arg) { os << arg; },
[&](const long arg) { os << arg; },
[&](const double arg) { os << arg; },
[&](const std::string& arg) {
std::string truncated = arg;
if (static_cast<size_t>(max_size) < arg.size()) {
truncated = arg.substr(0, max_size - 3) + "...";
}
os << truncated;
},
},
toConcreteObject());

return os.str();
} catch (RemoteMemCopyError& ex) {
LOG(WARNING) << std::hex << std::showbase << "Failed to create a repr for object of type "
<< d_classname << " at address " << d_addr;

return os.str();
std::stringstream os;
os << std::hex << std::showbase << "<" << d_classname << " object at " << d_addr << ">";
return os.str();
}
}

long
Expand Down
57 changes: 57 additions & 0 deletions tests/integration/test_local_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,3 +558,60 @@ def second_func(the_argument):

second_func_frame = find_frame(frames, "second_func")
assert "the_argument" not in second_func_frame.arguments


@ALL_PYTHONS
@ALL_SOURCES
def test_trashed_locals(generate_threads, python, tmpdir):
# GIVEN
_, python_executable = python

program_template = """
import ctypes
import sys
import time
class TupleObject(ctypes.Structure):
_fields_ = [
("ob_refcnt", ctypes.c_ssize_t),
("ob_type", ctypes.c_void_p),
("ob_size", ctypes.c_ssize_t),
("ob_item0", ctypes.c_void_p),
("ob_item1", ctypes.c_void_p),
]
def main():
bad_type = (1, 2, 3)
bad_elem = (4, 5, 6)
TupleObject.from_address(id(bad_type)).ob_type = 0xded
TupleObject.from_address(id(bad_elem)).ob_item1 = 0xbad
fifo = sys.argv[1]
with open(sys.argv[1], "w") as fifo:
fifo.write("ready")
time.sleep(1000)
main()
"""

script = tmpdir / "the_script.py"
script.write_text(program_template, encoding="utf-8")

# WHEN
threads = generate_threads(python_executable, script, tmpdir, locals=True)

# THEN

assert len(threads) == 1
(thread,) = threads

frames = list(thread.frames)
assert (len(frames)) == 2

main = find_frame(frames, "main")
assert re.match(
r"^<<invalid typeobject at 0xded> at 0x[0-9a-f]{4,}>$", main.locals["bad_type"]
)
assert re.match(r"^<tuple object at 0x[0-9a-f]{4,}>$", main.locals["bad_elem"])

0 comments on commit 0c7a734

Please sign in to comment.