Skip to content

Commit 3a9b436

Browse files
authored
fix: Call json_safe on globals in codejail remote_exec (#36542)
We need to make globals JSON-friendly before sending them across the network. Addresses edx/edx-arch-experiments#1016
1 parent 447cd79 commit 3a9b436

2 files changed

Lines changed: 48 additions & 2 deletions

File tree

xmodule/capa/safe_exec/remote_exec.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from importlib import import_module
88
import requests
99

10-
from codejail.safe_exec import SafeExecException
10+
from codejail.safe_exec import SafeExecException, json_safe
1111
from django.conf import settings
1212
from edx_toggles.toggles import SettingToggle
1313
from requests.exceptions import RequestException, HTTPError
@@ -90,7 +90,21 @@ def send_safe_exec_request_v0(data):
9090
extra_files = data.pop("extra_files")
9191

9292
codejail_service_endpoint = get_codejail_rest_service_endpoint()
93-
payload = json.dumps(data)
93+
94+
# In rare cases an XBlock might introduce `bytes` objects (or other
95+
# non-JSON-serializable objects) into the globals dict. The codejail service
96+
# (via the codejail library) will call `json_safe` on the globals before
97+
# JSON-encoding for the sandbox input, but here we need to call it earlier
98+
# in the process so we can even transport the globals *to* the codejail
99+
# service. Otherwise, we may get a TypeError when constructing the payload.
100+
#
101+
# This is a lossy operation (non-serializable objects will be dropped, and
102+
# bytes converted to strings) but it is the same lossy operation that
103+
# codejail will perform anyhow -- and it should be idempotent.
104+
data_send = {**data}
105+
data_send['globals_dict'] = json_safe(data_send['globals_dict'])
106+
107+
payload = json.dumps(data_send)
94108

95109
try:
96110
response = requests.post(
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Tests for remote codejail execution.
3+
"""
4+
5+
import json
6+
from unittest import TestCase
7+
from unittest.mock import patch
8+
9+
from django.test import override_settings
10+
11+
from xmodule.capa.safe_exec.remote_exec import get_remote_exec
12+
13+
14+
class TestRemoteExec(TestCase):
15+
"""Tests for remote_exec."""
16+
17+
@override_settings(
18+
ENABLE_CODEJAIL_REST_SERVICE=True,
19+
CODE_JAIL_REST_SERVICE_HOST='http://localhost',
20+
)
21+
@patch('requests.post')
22+
def test_json_encode(self, mock_post):
23+
get_remote_exec({
24+
'code': "out = 1 + 1",
25+
'globals_dict': {'some_data': b'bytes', 'unusable': object()},
26+
'extra_files': None,
27+
})
28+
29+
mock_post.assert_called_once()
30+
data_arg = mock_post.call_args_list[0][1]['data']
31+
payload = json.loads(data_arg['payload'])
32+
assert payload['globals_dict'] == {'some_data': 'bytes'}

0 commit comments

Comments
 (0)