Skip to content

Commit 51db1ad

Browse files
zoobalisroach
authored andcommitted
bpo-37363: Add audit events on startup for the run commands (pythonGH-14524)
1 parent 3ef3a03 commit 51db1ad

File tree

7 files changed

+190
-13
lines changed

7 files changed

+190
-13
lines changed

Doc/library/sys.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,12 @@ always available.
905905
read, so that you can set this hook there. The :mod:`site` module
906906
:ref:`sets this <rlcompleter-config>`.
907907

908+
.. audit-event:: cpython.run_interactivehook hook sys.__interactivehook__
909+
910+
Raises an :ref:`auditing event <auditing>`
911+
``cpython.run_interactivehook`` with the hook object as the argument when
912+
the hook is called on startup.
913+
908914
.. versionadded:: 3.4
909915

910916

Doc/tools/extensions/pyspecific.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,18 @@ def run(self):
199199
.format(name, info['args'], new_info['args'])
200200
)
201201

202-
if len(self.arguments) >= 3 and self.arguments[2]:
203-
target = self.arguments[2]
204-
ids = []
205-
else:
206-
target = "audit_event_{}_{}".format(name, len(info['source']))
207-
target = re.sub(r'\W', '_', label)
208-
ids = [target]
202+
ids = []
203+
try:
204+
target = self.arguments[2].strip("\"'")
205+
except (IndexError, TypeError):
206+
target = None
207+
if not target:
208+
target = "audit_event_{}_{}".format(
209+
re.sub(r'\W', '_', name),
210+
len(info['source']),
211+
)
212+
ids.append(target)
213+
209214
info['source'].append((env.docname, target))
210215

211216
pnode = nodes.paragraph(text, classes=["audit-hook"], ids=ids)
@@ -560,7 +565,8 @@ def process_audit_events(app, doctree, fromdocname):
560565
row += nodes.entry('', node)
561566

562567
node = nodes.paragraph()
563-
for i, (doc, label) in enumerate(audit_event['source'], start=1):
568+
backlinks = enumerate(sorted(set(audit_event['source'])), start=1)
569+
for i, (doc, label) in backlinks:
564570
if isinstance(label, str):
565571
ref = nodes.reference("", nodes.Text("[{}]".format(i)), internal=True)
566572
ref['refuri'] = "{}#{}".format(

Doc/using/cmdline.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ source.
7070
:data:`sys.path` (allowing modules in that directory to be imported as top
7171
level modules).
7272

73+
.. audit-event:: cpython.run_command command cmdoption-c
7374

7475
.. cmdoption:: -m <module-name>
7576

@@ -106,13 +107,14 @@ source.
106107
python -mtimeit -s 'setup here' 'benchmarked code here'
107108
python -mtimeit -h # for details
108109

110+
.. audit-event:: cpython.run_module module-name cmdoption-m
111+
109112
.. seealso::
110113
:func:`runpy.run_module`
111114
Equivalent functionality directly available to Python code
112115

113116
:pep:`338` -- Executing modules as scripts
114117

115-
116118
.. versionchanged:: 3.1
117119
Supply the package name to run a ``__main__`` submodule.
118120

@@ -129,6 +131,7 @@ source.
129131
``"-"`` and the current directory will be added to the start of
130132
:data:`sys.path`.
131133

134+
.. audit-event:: cpython.run_stdin "" ""
132135

133136
.. describe:: <script>
134137

@@ -148,6 +151,8 @@ source.
148151
added to the start of :data:`sys.path` and the ``__main__.py`` file in
149152
that location is executed as the :mod:`__main__` module.
150153

154+
.. audit-event:: cpython.run_file filename
155+
151156
.. seealso::
152157
:func:`runpy.run_path`
153158
Equivalent functionality directly available to Python code
@@ -540,6 +545,11 @@ conflict.
540545
the interactive session. You can also change the prompts :data:`sys.ps1` and
541546
:data:`sys.ps2` and the hook :data:`sys.__interactivehook__` in this file.
542547

548+
.. audit-event:: cpython.run_startup filename PYTHONSTARTUP
549+
550+
Raises an :ref:`auditing event <auditing>` ``cpython.run_startup`` with
551+
the filename as the argument when called on startup.
552+
543553

544554
.. envvar:: PYTHONOPTIMIZE
545555

Lib/test/test_embed.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ def setUp(self):
5757
def tearDown(self):
5858
os.chdir(self.oldcwd)
5959

60-
def run_embedded_interpreter(self, *args, env=None):
60+
def run_embedded_interpreter(self, *args, env=None,
61+
timeout=None, returncode=0, input=None):
6162
"""Runs a test in the embedded interpreter"""
6263
cmd = [self.test_exe]
6364
cmd.extend(args)
@@ -73,18 +74,18 @@ def run_embedded_interpreter(self, *args, env=None):
7374
universal_newlines=True,
7475
env=env)
7576
try:
76-
(out, err) = p.communicate()
77+
(out, err) = p.communicate(input=input, timeout=timeout)
7778
except:
7879
p.terminate()
7980
p.wait()
8081
raise
81-
if p.returncode != 0 and support.verbose:
82+
if p.returncode != returncode and support.verbose:
8283
print(f"--- {cmd} failed ---")
8384
print(f"stdout:\n{out}")
8485
print(f"stderr:\n{err}")
8586
print(f"------")
8687

87-
self.assertEqual(p.returncode, 0,
88+
self.assertEqual(p.returncode, returncode,
8889
"bad returncode %d, stderr is %r" %
8990
(p.returncode, err))
9091
return out, err
@@ -955,6 +956,37 @@ def test_audit(self):
955956
def test_audit_subinterpreter(self):
956957
self.run_embedded_interpreter("test_audit_subinterpreter")
957958

959+
def test_audit_run_command(self):
960+
self.run_embedded_interpreter("test_audit_run_command", timeout=3, returncode=1)
961+
962+
def test_audit_run_file(self):
963+
self.run_embedded_interpreter("test_audit_run_file", timeout=3, returncode=1)
964+
965+
def test_audit_run_interactivehook(self):
966+
startup = os.path.join(self.oldcwd, support.TESTFN) + ".py"
967+
with open(startup, "w", encoding="utf-8") as f:
968+
print("import sys", file=f)
969+
print("sys.__interactivehook__ = lambda: None", file=f)
970+
try:
971+
env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
972+
self.run_embedded_interpreter("test_audit_run_interactivehook", timeout=5,
973+
returncode=10, env=env)
974+
finally:
975+
os.unlink(startup)
976+
977+
def test_audit_run_startup(self):
978+
startup = os.path.join(self.oldcwd, support.TESTFN) + ".py"
979+
with open(startup, "w", encoding="utf-8") as f:
980+
print("pass", file=f)
981+
try:
982+
env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
983+
self.run_embedded_interpreter("test_audit_run_startup", timeout=5,
984+
returncode=10, env=env)
985+
finally:
986+
os.unlink(startup)
987+
988+
def test_audit_run_stdin(self):
989+
self.run_embedded_interpreter("test_audit_run_stdin", timeout=3, returncode=1)
958990

959991
if __name__ == "__main__":
960992
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Adds audit events for the range of supported run commands (see
2+
:ref:`using-on-general`).

Modules/main.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ pymain_run_command(wchar_t *command, PyCompilerFlags *cf)
247247
goto error;
248248
}
249249

250+
if (PySys_Audit("cpython.run_command", "O", unicode) < 0) {
251+
return pymain_exit_err_print();
252+
}
253+
250254
bytes = PyUnicode_AsUTF8String(unicode);
251255
Py_DECREF(unicode);
252256
if (bytes == NULL) {
@@ -267,6 +271,9 @@ static int
267271
pymain_run_module(const wchar_t *modname, int set_argv0)
268272
{
269273
PyObject *module, *runpy, *runmodule, *runargs, *result;
274+
if (PySys_Audit("cpython.run_module", "u", modname) < 0) {
275+
return pymain_exit_err_print();
276+
}
270277
runpy = PyImport_ImportModule("runpy");
271278
if (runpy == NULL) {
272279
fprintf(stderr, "Could not import runpy module\n");
@@ -311,6 +318,9 @@ static int
311318
pymain_run_file(PyConfig *config, PyCompilerFlags *cf)
312319
{
313320
const wchar_t *filename = config->run_filename;
321+
if (PySys_Audit("cpython.run_file", "u", filename) < 0) {
322+
return pymain_exit_err_print();
323+
}
314324
FILE *fp = _Py_wfopen(filename, L"rb");
315325
if (fp == NULL) {
316326
char *cfilename_buffer;
@@ -383,6 +393,9 @@ pymain_run_startup(PyConfig *config, PyCompilerFlags *cf, int *exitcode)
383393
if (startup == NULL) {
384394
return 0;
385395
}
396+
if (PySys_Audit("cpython.run_startup", "s", startup) < 0) {
397+
return pymain_err_print(exitcode);
398+
}
386399

387400
FILE *fp = _Py_fopen(startup, "r");
388401
if (fp == NULL) {
@@ -420,6 +433,10 @@ pymain_run_interactive_hook(int *exitcode)
420433
return 0;
421434
}
422435

436+
if (PySys_Audit("cpython.run_interactivehook", "O", hook) < 0) {
437+
goto error;
438+
}
439+
423440
result = _PyObject_CallNoArg(hook);
424441
Py_DECREF(hook);
425442
if (result == NULL) {
@@ -457,6 +474,10 @@ pymain_run_stdin(PyConfig *config, PyCompilerFlags *cf)
457474
return pymain_exit_err_print();
458475
}
459476

477+
if (PySys_Audit("cpython.run_stdin", NULL) < 0) {
478+
return pymain_exit_err_print();
479+
}
480+
460481
int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, cf);
461482
return (run != 0);
462483
}

Programs/_testembed.c

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,101 @@ static int test_audit_subinterpreter(void)
12351235
}
12361236
}
12371237

1238+
typedef struct {
1239+
const char* expected;
1240+
int exit;
1241+
} AuditRunCommandTest;
1242+
1243+
static int _audit_hook_run(const char *eventName, PyObject *args, void *userData)
1244+
{
1245+
AuditRunCommandTest *test = (AuditRunCommandTest*)userData;
1246+
if (strcmp(eventName, test->expected)) {
1247+
return 0;
1248+
}
1249+
1250+
if (test->exit) {
1251+
PyObject *msg = PyUnicode_FromFormat("detected %s(%R)", eventName, args);
1252+
if (msg) {
1253+
printf("%s\n", PyUnicode_AsUTF8(msg));
1254+
Py_DECREF(msg);
1255+
}
1256+
exit(test->exit);
1257+
}
1258+
1259+
PyErr_Format(PyExc_RuntimeError, "detected %s(%R)", eventName, args);
1260+
return -1;
1261+
}
1262+
1263+
static int test_audit_run_command(void)
1264+
{
1265+
AuditRunCommandTest test = {"cpython.run_command"};
1266+
wchar_t *argv[] = {L"./_testembed", L"-c", L"pass"};
1267+
1268+
Py_IgnoreEnvironmentFlag = 0;
1269+
PySys_AddAuditHook(_audit_hook_run, (void*)&test);
1270+
1271+
return Py_Main(Py_ARRAY_LENGTH(argv), argv);
1272+
}
1273+
1274+
static int test_audit_run_file(void)
1275+
{
1276+
AuditRunCommandTest test = {"cpython.run_file"};
1277+
wchar_t *argv[] = {L"./_testembed", L"filename.py"};
1278+
1279+
Py_IgnoreEnvironmentFlag = 0;
1280+
PySys_AddAuditHook(_audit_hook_run, (void*)&test);
1281+
1282+
return Py_Main(Py_ARRAY_LENGTH(argv), argv);
1283+
}
1284+
1285+
static int run_audit_run_test(int argc, wchar_t **argv, void *test)
1286+
{
1287+
PyStatus status;
1288+
PyConfig config;
1289+
status = PyConfig_InitPythonConfig(&config);
1290+
if (PyStatus_Exception(status)) {
1291+
Py_ExitStatusException(status);
1292+
}
1293+
config.argv.length = argc;
1294+
config.argv.items = argv;
1295+
config.parse_argv = 1;
1296+
config.program_name = argv[0];
1297+
config.interactive = 1;
1298+
config.isolated = 0;
1299+
config.use_environment = 1;
1300+
config.quiet = 1;
1301+
1302+
PySys_AddAuditHook(_audit_hook_run, test);
1303+
1304+
status = Py_InitializeFromConfig(&config);
1305+
if (PyStatus_Exception(status)) {
1306+
Py_ExitStatusException(status);
1307+
}
1308+
1309+
return Py_RunMain();
1310+
}
1311+
1312+
static int test_audit_run_interactivehook(void)
1313+
{
1314+
AuditRunCommandTest test = {"cpython.run_interactivehook", 10};
1315+
wchar_t *argv[] = {L"./_testembed"};
1316+
return run_audit_run_test(Py_ARRAY_LENGTH(argv), argv, &test);
1317+
}
1318+
1319+
static int test_audit_run_startup(void)
1320+
{
1321+
AuditRunCommandTest test = {"cpython.run_startup", 10};
1322+
wchar_t *argv[] = {L"./_testembed"};
1323+
return run_audit_run_test(Py_ARRAY_LENGTH(argv), argv, &test);
1324+
}
1325+
1326+
static int test_audit_run_stdin(void)
1327+
{
1328+
AuditRunCommandTest test = {"cpython.run_stdin"};
1329+
wchar_t *argv[] = {L"./_testembed"};
1330+
return run_audit_run_test(Py_ARRAY_LENGTH(argv), argv, &test);
1331+
}
1332+
12381333
static int test_init_read_set(void)
12391334
{
12401335
PyStatus status;
@@ -1413,6 +1508,11 @@ static struct TestCase TestCases[] = {
14131508
{"test_open_code_hook", test_open_code_hook},
14141509
{"test_audit", test_audit},
14151510
{"test_audit_subinterpreter", test_audit_subinterpreter},
1511+
{"test_audit_run_command", test_audit_run_command},
1512+
{"test_audit_run_file", test_audit_run_file},
1513+
{"test_audit_run_interactivehook", test_audit_run_interactivehook},
1514+
{"test_audit_run_startup", test_audit_run_startup},
1515+
{"test_audit_run_stdin", test_audit_run_stdin},
14161516
{NULL, NULL}
14171517
};
14181518

0 commit comments

Comments
 (0)