55
66#ifdef HAVE_PERF_TRAMPOLINE
77
8+ #include <fcntl.h>
89#include <stdio.h>
910#include <stdlib.h>
1011#include <sys/mman.h>
@@ -29,6 +30,12 @@ struct code_arena_st {
2930
3031typedef struct code_arena_st code_arena_t ;
3132
33+ typedef enum {
34+ PERF_STATUS_FAILED = -1 ,
35+ PERF_STATUS_NO_INIT = 0 ,
36+ } perf_status_t ;
37+
38+ static perf_status_t perf_status = PERF_STATUS_NO_INIT ;
3239static Py_ssize_t extra_code_index = -1 ;
3340static code_arena_t * code_arena ;
3441static FILE * perf_map_file ;
@@ -46,7 +53,10 @@ new_code_arena(void)
4653 -1 , // fd (not used here)
4754 0 ); // offset (not used here)
4855 if (!memory ) {
49- Py_FatalError ("Failed to allocate new code arena" );
56+ PyErr_SetFromErrno (PyExc_OSError );
57+ _PyErr_WriteUnraisableMsg (
58+ "Failed to create new mmap for perf trampoline" , NULL );
59+ perf_status = PERF_STATUS_FAILED ;
5060 return -1 ;
5161 }
5262 void * start = & _Py_trampoline_func_start ;
@@ -57,12 +67,21 @@ new_code_arena(void)
5767 for (size_t i = 0 ; i < n_copies ; i ++ ) {
5868 memcpy (memory + i * code_size , start , code_size * sizeof (char ));
5969 }
60-
61- mprotect (memory , mem_size , PROT_READ | PROT_EXEC );
70+ // Some systems may prevent us from creating executable code on the fly.
71+ int res = mprotect (memory , mem_size , PROT_READ | PROT_EXEC );
72+ if (res == -1 ) {
73+ PyErr_SetFromErrno (PyExc_OSError );
74+ munmap (memory , mem_size );
75+ _PyErr_WriteUnraisableMsg (
76+ "Failed to set mmap for perf trampoline to PROT_READ | PROT_EXEC" , NULL );
77+ }
6278
6379 code_arena_t * new_arena = PyMem_RawCalloc (1 , sizeof (code_arena_t ));
6480 if (new_arena == NULL ) {
65- Py_FatalError ("Failed to allocate new code arena struct" );
81+ PyErr_NoMemory ();
82+ munmap (memory , mem_size );
83+ _PyErr_WriteUnraisableMsg (
84+ "Failed to allocate new code arena struct" , NULL );
6685 return -1 ;
6786 }
6887
@@ -107,7 +126,6 @@ compile_trampoline(void)
107126 return NULL ;
108127 }
109128 }
110-
111129 assert (code_arena -> size_left <= code_arena -> size );
112130 return code_arena_new_code (code_arena );
113131}
@@ -120,11 +138,23 @@ perf_map_get_file(void)
120138 }
121139 char filename [100 ];
122140 pid_t pid = getpid ();
123- // TODO: %d is incorrect if pid_t is long long
124- snprintf (filename , sizeof (filename ), "/tmp/perf-%d.map" , pid );
125- perf_map_file = fopen (filename , "a" );
141+ // Location and file name of perf map is hard-coded in perf tool.
142+ // Use exclusive create flag wit nofollow to prevent symlink attacks.
143+ int flags = O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC ;
144+ snprintf (filename , sizeof (filename )- 1 , "/tmp/perf-%jd.map" , (intmax_t )pid );
145+ int fd = open (filename , flags , 0600 );
146+ if (fd == -1 ) {
147+ perf_status = PERF_STATUS_FAILED ;
148+ PyErr_SetFromErrnoWithFilename (PyExc_OSError , filename );
149+ _PyErr_WriteUnraisableMsg ("Failed to create perf map file" , NULL );
150+ return NULL ;
151+ }
152+ perf_map_file = fdopen (fd , "w" );
126153 if (!perf_map_file ) {
127- _Py_FatalErrorFormat (__func__ , "Couldn't open %s: errno(%d)" , filename , errno );
154+ perf_status = PERF_STATUS_FAILED ;
155+ PyErr_SetFromErrnoWithFilename (PyExc_OSError , filename );
156+ close (fd );
157+ _PyErr_WriteUnraisableMsg ("Failed to create perf map file handle" , NULL );
128158 return NULL ;
129159 }
130160 return perf_map_file ;
@@ -136,6 +166,7 @@ perf_map_close(FILE *fp)
136166 if (fp ) {
137167 return fclose (fp );
138168 }
169+ perf_status = PERF_STATUS_NO_INIT ;
139170 return 0 ;
140171}
141172
@@ -154,20 +185,23 @@ static PyObject *
154185py_trampoline_evaluator (PyThreadState * ts , _PyInterpreterFrame * frame ,
155186 int throw )
156187{
188+ if (perf_status == PERF_STATUS_FAILED ) {
189+ return _PyEval_EvalFrameDefault (ts , frame , throw );
190+ }
157191 PyCodeObject * co = frame -> f_code ;
158192 py_trampoline f = NULL ;
159193 _PyCode_GetExtra ((PyObject * )co , extra_code_index , (void * * )& f );
160194 if (f == NULL ) {
161195 FILE * pfile = perf_map_get_file ();
162196 if (pfile == NULL ) {
163- return NULL ;
197+ return _PyEval_EvalFrameDefault ( ts , frame , throw ) ;
164198 }
165199 if (extra_code_index == -1 ) {
166200 extra_code_index = _PyEval_RequestCodeExtraIndex (NULL );
167201 }
168202 py_trampoline new_trampoline = compile_trampoline ();
169203 if (new_trampoline == NULL ) {
170- return NULL ;
204+ return _PyEval_EvalFrameDefault ( ts , frame , throw ) ;
171205 }
172206 perf_map_write_entry (pfile , new_trampoline , code_arena -> code_size ,
173207 PyUnicode_AsUTF8 (co -> co_qualname ),
0 commit comments