Skip to content

Commit 7bcaa49

Browse files
authored
donate-cpu-server: do not serve files outside the result path (#8945)
the fallback branch in HttpClientThread.run serves self.resultPath + url for any GET path that is not a known report, and the path from parse_req is not normalized. a raw request like `GET /../../../etc/passwd HTTP/1.1` (browsers collapse the dots, a socket or `curl --path-as-is` does not) gets back any text file the daca server user can read. normalize the joined path and answer 404 unless it stays under the result path, so package results and info_output/ files are served as before. test_file_outside_result_path in test/tools/donate_cpu_server_test.py fails without the change. there is no trac ticket for this yet, please create one or let me know and i will update the title.
1 parent ad19835 commit 7bcaa49

2 files changed

Lines changed: 32 additions & 2 deletions

File tree

‎test/tools/donate_cpu_server_test.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,32 @@ def test_parse_req():
1515
_test_parse_req("GET /head-uninitvar?pkgs=1 HTTP/1.1", '/head-uninitvar', {'pkgs': '1'})
1616
_test_parse_req("GET /crash.html?pkgs=1 HTTP/1.1", '/crash.html', {'pkgs': '1'})
1717
_test_parse_req("GET /head-uninitvar?pkgs=1&pkgs2=2 HTTP/1.1", '/head-uninitvar', {'pkgs': '1', 'pkgs2': '2'})
18+
19+
class _Connection:
20+
def __init__(self):
21+
self.data = b''
22+
23+
def send(self, data):
24+
self.data += data
25+
return len(data)
26+
27+
def close(self):
28+
pass
29+
30+
def _http_get(result_path, url):
31+
conn = _Connection()
32+
donate_cpu_server.HttpClientThread(conn, 'GET {} HTTP/1.1'.format(url), str(result_path), []).run()
33+
return conn.data.decode('utf-8')
34+
35+
def test_file_outside_result_path(tmp_path):
36+
result_path = tmp_path / 'donated-results'
37+
result_path.mkdir()
38+
(result_path / 'pkg').write_text('result data')
39+
(tmp_path / 'secret.txt').write_text('secret data')
40+
41+
resp = _http_get(result_path, '/pkg')
42+
assert resp.startswith('HTTP/1.1 200 OK') and resp.endswith('result data')
43+
44+
resp = _http_get(result_path, '/../secret.txt')
45+
assert resp.startswith('HTTP/1.1 404 Not Found')
46+
assert 'secret data' not in resp

‎tools/donate-cpu-server.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,8 +1275,9 @@ def run(self):
12751275
text = clientsReport(self.resultPath)
12761276
httpGetResponse(self.connection, text, 'text/html')
12771277
else:
1278-
filename = self.resultPath + url
1279-
if not os.path.isfile(filename):
1278+
result_path = os.path.normpath(self.resultPath)
1279+
filename = os.path.normpath(result_path + url)
1280+
if os.path.commonpath([result_path, filename]) != result_path or not os.path.isfile(filename):
12801281
print_ts('HTTP/1.1 404 Not Found')
12811282
self.connection.send(b'HTTP/1.1 404 Not Found\r\n\r\n')
12821283
else:

0 commit comments

Comments
 (0)