Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Raise Exception on unwanted syscall

I was told to fix a bug in a legacy application.

I can reproduce a bug, but I have no clue at which python source code line the error does get executed.

I can see the relevant failure with strace: A file gets opened, which should not get opened.

I would like to make the relevant open() linux-syscall raise an Exception in the python interpreter. My goal: I want to see the stacktrace to be able to fix the bug.

This way I could avoid the time consuming stepping through a lot of lines with a debugger.

The same with other words: if the syscall gets executed, which results in the strace output of open("/somefile", O_RDONLY) = 4 the python interpreter should exit with an traceback.

Has anybody a solution?

Please leave a comment if you don't understand what I am looking for.

like image 350
guettli Avatar asked Jan 05 '23 11:01

guettli


2 Answers

We can do a patch on open before import modules, here is an example:

in test.py:

def func():
    with open('test', 'w') as f:
        pass

in test2.py:

try:
    import __builtin__ # for python2
except ImportError:
    import builtins as __builtin__ #for python3

import copy
import traceback

orig_open = copy.copy(__builtin__.open)

def myopen(*args):
    traceback.print_stack()
    return orig_open(*args)

__builtin__.open = myopen

from test import func # Note that we import the module after patching on open()

func()

and when func() is called in test2.py, call stack will be printed:

$ python test2.py 
  File "test2.py", line 19, in <module>
    func()
  File "/tmp/test.py", line 4, in func
    with open('test', 'w') as f:
  File "test2.py", line 12, in myopen
    traceback.print_stack()
like image 136
zsrkmyn Avatar answered Jan 13 '23 17:01

zsrkmyn


You can run python under gdb, set a (conditional) breakpoint on the open() syscall (or, rather, the stub function in libc through which it is called), and, when the breakpoint is hit, send a SIGINT signal to the python process and let it continue, whereupon the execution of the python script should be interrupted with desired stack trace.

The shell script below automates that procedure.

Usage:

stack_trace_on_open filename -- python script.py [script args]


stack_trace_on_open:

#!/usr/bin/env bash

myname="$(basename "$0")"

if [[ $# -lt 4 || "$2" != '--' ]]
then
    echo >&2 "Usage: $myname <filename> -- python <script.py> [script args ...]"
    exit 1
fi

fname=$1
python_exe="$3"
shift 3

gdb -q "$python_exe" <<END
set breakpoint pending on
break open
condition 1 strcmp(\$rdi,"$fname") == 0
run "$@"
signal 2
cont
quit
END

Demonstration:

$ cat test.py 
import ctypes

clib = ctypes.CDLL(None)
fd = clib.open("/dev/urandom", 0)
clib.close(fd)

$ ./stack_trace_on_open /dev/urandom -- python test.py 
Reading symbols from python...(no debugging symbols found)...done.
(gdb) (gdb) Function "open" not defined.
Breakpoint 1 (open) pending.
(gdb) (gdb) Starting program: /usr/bin/python "test.py"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, open64 () at ../sysdeps/unix/syscall-template.S:84
84  ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) Continuing with signal SIGINT.

Breakpoint 1, open64 () at ../sysdeps/unix/syscall-template.S:84
84  in ../sysdeps/unix/syscall-template.S
(gdb) Continuing.
Traceback (most recent call last):                #     <--------
  File "test.py", line 4, in <module>             #     <--------
    fd = clib.open("/dev/urandom", 0)             #     <--------
KeyboardInterrupt
[Inferior 1 (process 14248) exited with code 01]
(gdb)
like image 43
Leon Avatar answered Jan 13 '23 17:01

Leon