forked from its-ME-007/DAALAB
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode_runner.py
More file actions
204 lines (170 loc) · 7.31 KB
/
Copy pathcode_runner.py
File metadata and controls
204 lines (170 loc) · 7.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import subprocess
import tempfile
import os
import time
import signal
import sys
from typing import Tuple, Optional
import docker
from contextlib import contextmanager
class SafeCodeRunner:
"""
A safe code runner with fallback mechanisms:
1. Docker container (preferred - most secure)
2. Local execution with restrictions (fallback)
"""
def __init__(self):
self.docker_available = self._check_docker()
self.image_name = "python:3.13-slim"
self.timeout_seconds = 10 # Maximum execution time
# Forbidden modules and functions for local execution
self.forbidden_modules = {
'os', 'sys', 'subprocess', 'multiprocessing', 'threading',
'socket', 'urllib', 'requests', 'http', 'ftplib', 'smtplib',
'sqlite3', 'pickle', 'marshal', 'ctypes', 'mmap', 'fcntl',
'pwd', 'grp', 'crypt', 'termios', 'tty', 'pty', 'signal',
'pipes', 'posix', 'nt', 'mac', 'dummy_threading', 'concurrent'
}
self.forbidden_functions = {
'eval', 'exec', 'compile', 'input', 'raw_input',
'open', 'file', 'reload', 'importlib.reload'
}
def _check_docker(self) -> bool:
"""Check if Docker is available and running"""
try:
client = docker.from_env()
client.ping()
return True
except Exception:
return False
def _check_code_safety(self, code: str) -> Tuple[bool, str]:
"""
Check if code is safe to run locally
Returns: (is_safe, error_message)
"""
# Check for forbidden imports
lines = code.split('\n')
for line in lines:
line = line.strip()
if line.startswith('import ') or line.startswith('from '):
# Extract module name
if line.startswith('import '):
module = line[7:].split()[0].split('.')[0]
else: # from ... import
module = line[5:].split()[0].split('.')[0]
if module in self.forbidden_modules:
return False, f"Forbidden module: {module}"
# Check for forbidden function calls
for func in self.forbidden_functions:
if func in code:
return False, f"Forbidden function: {func}"
# Check for potentially dangerous patterns
dangerous_patterns = [
'__import__', 'globals()', 'locals()', 'vars()', 'dir()',
'getattr', 'setattr', 'delattr', 'hasattr',
'type', 'isinstance', 'issubclass',
'super', 'property', 'staticmethod', 'classmethod'
]
for pattern in dangerous_patterns:
if pattern in code:
return False, f"Dangerous pattern: {pattern}"
return True, ""
def _run_in_docker(self, code: str) -> Tuple[str, float]:
"""Run code in Docker container"""
try:
client = docker.from_env()
# Create temporary file
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(code)
temp_file = f.name
try:
# Run container
start_time = time.time()
container = client.containers.run(
self.image_name,
command=["python", "-u", "/code/code.py"],
volumes={
temp_file: {"bind": "/code/code.py", "mode": "ro"}
},
remove=True,
detach=False,
timeout=self.timeout_seconds
)
runtime = time.time() - start_time
output = container.decode('utf-8') if container else "No output generated"
return output, runtime
finally:
# Clean up temporary file
os.unlink(temp_file)
except Exception as e:
return f"Error: {str(e)}", 0.0
def _run_locally(self, code: str) -> Tuple[str, float]:
"""Run code locally with restrictions"""
try:
# Create temporary file
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(code)
temp_file = f.name
try:
# Set up process with timeout
start_time = time.time()
# Run with restricted environment
env = os.environ.copy()
env['PYTHONPATH'] = '' # Clear Python path
env['PYTHONHOME'] = '' # Clear Python home
process = subprocess.Popen(
[sys.executable, temp_file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
text=True,
preexec_fn=os.setsid if hasattr(os, 'setsid') else None
)
try:
stdout, stderr = process.communicate(timeout=self.timeout_seconds)
runtime = time.time() - start_time
if process.returncode == 0:
output = stdout if stdout else "No output generated"
if stderr:
output += f"\nWarnings: {stderr}"
else:
output = f"Error: {stderr}" if stderr else "Execution failed"
return output, runtime
except subprocess.TimeoutExpired:
# Kill the process group
if hasattr(os, 'killpg'):
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
else:
process.terminate()
process.wait()
return "Error: Execution timeout exceeded", 0.0
finally:
# Clean up temporary file
os.unlink(temp_file)
except Exception as e:
return f"Error: {str(e)}", 0.0
def run_code(self, code: str) -> Tuple[str, float]:
"""
Run Python code with fallback mechanisms
Returns: (output, runtime)
"""
if not code.strip():
return "Error: Code cannot be empty", 0.0
# Try Docker first if available
if self.docker_available:
try:
return self._run_in_docker(code)
except Exception as e:
print(f"Docker execution failed: {e}")
# Fall back to local execution
# Check if code is safe for local execution
is_safe, error_msg = self._check_code_safety(code)
if not is_safe:
return f"Error: {error_msg}. Docker is required for this code.", 0.0
# Run locally with restrictions
return self._run_locally(code)
# Backward compatibility
class ContainerRunner(SafeCodeRunner):
"""Backward compatibility wrapper"""
def __init__(self):
super().__init__()