Skip to content
This repository was archived by the owner on Jan 23, 2024. It is now read-only.

Commit 14e0348

Browse files
vladlfb-daniels
authored andcommitted
Best effort to block particular kind of mutable expressions in Python Cloud Debugger.
The problem is with expressions like x.__setattr__('a', 1). For some reason Python just decides to skip the trace call, which makes calls to primitives like __setattr__ invisible for ImmutabilityTraces. This CL implements a best effort solution (which is by no mean complete). We scan names of a code object and block it if there is a string like "__setattr__". It is possible by bypass it, but it should cover vast majority of the cases when the user runs such expressions by mistake. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=112289038
1 parent 90b0f6b commit 14e0348

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

src/googleclouddebugger/immutability_tracer.cc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ static const char* kWhitelistedCFunctions[] = {
9898
};
9999

100100

101+
static const char* kBlacklistedCodeObjectNames[] = {
102+
"__setattr__",
103+
"__delattr__",
104+
"__del__",
105+
"__new__",
106+
"__set__",
107+
"__delete__",
108+
"__call__",
109+
"__setitem__",
110+
"__delitem__",
111+
"__setslice__",
112+
"__delslice__",
113+
};
114+
101115
ImmutabilityTracer::ImmutabilityTracer()
102116
: self_(nullptr),
103117
thread_state_(nullptr),
@@ -152,6 +166,7 @@ int ImmutabilityTracer::OnTraceCallbackInternal(
152166
PyObject* arg) {
153167
switch (what) {
154168
case PyTrace_CALL:
169+
VerifyCodeObject(ScopedPyCodeObject::NewReference(frame->f_code));
155170
break;
156171

157172
case PyTrace_EXCEPTION:
@@ -191,6 +206,48 @@ int ImmutabilityTracer::OnTraceCallbackInternal(
191206
}
192207

193208

209+
void ImmutabilityTracer::VerifyCodeObject(ScopedPyCodeObject code_object) {
210+
if (code_object == nullptr) {
211+
return;
212+
}
213+
214+
if (verified_code_objects_.count(code_object) != 0) {
215+
return;
216+
}
217+
218+
// Try to block expressions like "x.__setattr__('a', 1)". Python interpreter
219+
// doesn't generate any trace callback for calls to built-in primitives like
220+
// "__setattr__". Our best effort is to enumerate over all names in the code
221+
// object and block ones with names like "__setprop__". The user can still
222+
// bypass it, so this is just best effort.
223+
PyObject* names = code_object.get()->co_names;
224+
if ((names == nullptr) || !PyTuple_CheckExact(names)) {
225+
LOG(WARNING) << "Corrupted code object: co_names is not a valid tuple";
226+
mutable_code_detected_ = true;
227+
return;
228+
}
229+
230+
int count = PyTuple_GET_SIZE(names);
231+
for (int i = 0; i != count; ++i) {
232+
const char* name = PyString_AsString(PyTuple_GET_ITEM(names, i));
233+
if (name == nullptr) {
234+
LOG(WARNING) << "Corrupted code object: name " << i << " is not a string";
235+
mutable_code_detected_ = true;
236+
return;
237+
}
238+
239+
for (int j = 0; j != arraysize(kBlacklistedCodeObjectNames); ++j) {
240+
if (!strcmp(kBlacklistedCodeObjectNames[j], name)) {
241+
mutable_code_detected_ = true;
242+
return;
243+
}
244+
}
245+
}
246+
247+
verified_code_objects_.insert(code_object);
248+
}
249+
250+
194251
void ImmutabilityTracer::ProcessCodeLine(
195252
PyCodeObject* code_object,
196253
int line_number) {

src/googleclouddebugger/immutability_tracer.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#ifndef DEVTOOLS_CDBG_DEBUGLETS_PYTHON_IMMUTABILITY_TRACER_H_
1818
#define DEVTOOLS_CDBG_DEBUGLETS_PYTHON_IMMUTABILITY_TRACER_H_
1919

20+
#include <unordered_set>
2021
#include "common.h"
2122
#include "python_util.h"
2223

@@ -70,6 +71,9 @@ class ImmutabilityTracer {
7071
// Python tracer callback function (instance function for convenience).
7172
int OnTraceCallbackInternal(PyFrameObject* frame, int what, PyObject* arg);
7273

74+
// Verifies that the code object doesn't include calls to blocked primitives.
75+
void VerifyCodeObject(ScopedPyCodeObject code_object);
76+
7377
// Verifies immutability of code on a single line.
7478
void ProcessCodeLine(PyCodeObject* code_object, int line_number);
7579

@@ -93,6 +97,11 @@ class ImmutabilityTracer {
9397
// Evaluation thread.
9498
PyThreadState* thread_state_;
9599

100+
// Set of code object verified to not have any blocked primitives.
101+
std::unordered_set<
102+
ScopedPyCodeObject,
103+
ScopedPyCodeObject::Hash> verified_code_objects_;
104+
96105
// Original value of PyThreadState::tracing. We revert it to 0 to enforce
97106
// trace callback on this thread, even if the whole thing was executed from
98107
// within another trace callback (that caught the breakpoint).

0 commit comments

Comments
 (0)