commit d9f95490d15da221844b066784149f68db92cc5d
parent 9763bd5c6d2ab498e989b738c35884c9d7bf8d5e
Author: Louis Burda <quent.burda@gmail.com>
Date: Sat, 26 Jun 2021 13:28:18 +0200
add checking of returned stl info in test_good_upload havocs
Diffstat:
5 files changed, 93 insertions(+), 16 deletions(-)
diff --git a/checker/src/checker.py b/checker/src/checker.py
@@ -1,8 +1,10 @@
#!/usr/bin/env python3
from enochecker import BaseChecker, BrokenServiceException, EnoException, run
-from enochecker.utils import SimpleSocket, assert_equals, assert_in
-import math, os, random, string, struct, subprocess, logging, selectors, time, socket
+from enochecker.utils import SimpleSocket
+import logging, math, os, random, re, socket, string, struct, subprocess, selectors, time
import numpy as np
+from io import BytesIO
+from stl import mesh
logging.getLogger("faker").setLevel(logging.WARNING)
logging.getLogger("pwnlib").setLevel(logging.WARNING)
@@ -56,6 +58,9 @@ def havocid():
idlen = rand.randint(10, 40)
return bytes([rand.randint(32, 127) for i in range(idlen)])
+def approx_equal(f1, f2, precision = 2):
+ return round(f1, precision) == round(f2, precision)
+
def reverse_hash(hashstr):
if type(hashstr) is bytes:
hashstr = hashstr.decode()
@@ -76,6 +81,20 @@ def parse_int(intstr):
except:
return None
+def parse_float(floatstr):
+ try:
+ return float(floatstr)
+ except:
+ return None
+
+def assert_match(data, pattern, exception):
+ rem = re.search(pattern, data)
+ if rem is None:
+ raise exception(f"Expected pattern {pattern} to match {data}")
+ if len(rem.groups()) > 0:
+ return rem.group(1)
+ return rem.group(0)
+
class STLDoctorChecker(BaseChecker):
service_name = "stldoctor"
port = 9090
@@ -190,12 +209,39 @@ class STLDoctorChecker(BaseChecker):
elif filetype == "bin":
return self.genfile_bin(solidname, malformed = malformed)
elif filetype == "garbage-tiny":
- return ("".join([rand.choice(generic_alphabet) for i in range(rand.randint(3, 8))])).encode()
+ return bytes([ord(rand.choice(generic_alphabet)) for i in range(rand.randint(3, 8))])
elif filetype == "garbage":
- return ("".join([rand.choice(generic_alphabet) for i in range(rand.randint(100, 300))])).encode()
+ return bytes([ord(rand.choice(generic_alphabet)) for i in range(rand.randint(100, 300))])
else:
raise EnoException("Invalid file type supplied")
+ def parse_stlinfo(self, stlfile):
+ fakefile = BytesIO()
+ fakefile.write(stlfile)
+ fakefile.seek(0)
+ try:
+ name, data = mesh.Mesh.load(fakefile)
+ meshinfo = mesh.Mesh(data, True, name=name, speedups=True)
+ except Exception as e:
+ raise BrokenServiceException(f"STL file parsing failed: {e}")
+ bmin = [math.inf for i in range(3)]
+ bmax = [-math.inf for i in range(3)]
+ if len(meshinfo.points) == 0:
+ raise EnoException("Parsed STL mesh has 0 points!")
+ for p in meshinfo.points:
+ for k in range(3):
+ for i in range(3):
+ bmin[k] = min(bmin[k], float(p[3*i+k]))
+ bmax[k] = max(bmax[k], float(p[3*i+k]))
+ info = {
+ "points": meshinfo.points,
+ "bb_origin": bmin,
+ "bb_size": [bmax[i] - bmin[i] for i in range(3)],
+ "size": len(stlfile),
+ "triangle_count": len(meshinfo.points)
+ }
+ return info
+
def openconn(self):
conn = self.connect()
resp = conn.recvuntil(b'\n' + self.prompt)
@@ -296,7 +342,8 @@ class STLDoctorChecker(BaseChecker):
stlfile = b""
if download: # Parse file contents
conn.recvuntil(b"Here you go.. (")
- size = parse_int(conn.recvuntil(b"B)\n")[:-3])
+ resp = conn.recvuntil(b"B)\n")[:-3]
+ size = parse_int(resp)
if size is None:
raise BrokenServiceException(f"Received invalid download size, response:\n{resp}")
@@ -342,6 +389,29 @@ class STLDoctorChecker(BaseChecker):
raise BrokenServiceException(f"Unexpectedly {modelname} info contains one of {includes}: {combined}")
return resp
+ def check_stlinfo(self, resp, ref_info):
+ size = parse_int(assert_match(resp, b"File Size: (.*)\n", BrokenServiceException))
+ if not size or size != ref_info["size"]:
+ raise BrokenServiceException(f"STL info returned no / invalid file size: {size} != {ref_info['size']}")
+
+ triangle_count = parse_int(assert_match(resp, b"Triangle Count: (.*)\n", BrokenServiceException))
+ if not triangle_count or triangle_count != ref_info["triangle_count"]:
+ raise BrokenServiceException(f"STL info returned no / invalid triangle count: {triangle_count} != {ref_info['triangle_count']}")
+
+ bb_size_str = assert_match(resp, b"Bounding Box Size: (.*)\n", BrokenServiceException)
+ bb_size = [parse_float(v) for v in bb_size_str.split(b" x ")]
+ if None in bb_size:
+ raise BrokenServiceException(f"STL info returned invalid bounding box size: {bb_size_str}")
+ if False in [approx_equal(bb_size[i], ref_info["bb_size"][i]) for i in range(3)]:
+ raise BrokenServiceException(f"Returned bounding box size is too far off: (REF) {ref_info['bb_size']} {bb_size}")
+
+ bb_origin_str = assert_match(resp, b"Bounding Box Origin: (.*)\n", BrokenServiceException)
+ bb_origin = [parse_float(v) for v in bb_origin_str.split(b" x ")]
+ if None in bb_origin:
+ raise BrokenServiceException(f"STL info returned invalid bounding box origin: {bb_origin_str}")
+ if False in [approx_equal(bb_origin[i], ref_info["bb_origin"][i]) for i in range(3)]:
+ raise BrokenServiceException(f"Returned bounding box origin is too far off: (REF) {ref_info['bb_origin']} {bb_origin}")
+
# TEST METHODS #
def test_good_upload(self, filetype, register):
@@ -351,13 +421,17 @@ class STLDoctorChecker(BaseChecker):
authstr = havocid()
stlfile = self.genfile(solidname, filetype)
+ # Calculate properties to test response against
+ ref_info = self.parse_stlinfo(stlfile)
+
# Create new session and user and upload file
conn = self.openconn()
if register:
self.do_auth(conn, authstr)
modelid = self.do_upload(conn, modelname, stlfile)
expected = [modelname, solidname, stlfile, modelid]
- resp = self.check_in_search(conn, modelname, expected, download = True)
+ info, stlfile = self.check_in_search(conn, modelname, expected, download = True)
+ self.check_stlinfo(info, ref_info)
if register:
resp = self.check_listed(conn, [modelname, modelid])
self.closeconn(conn)
@@ -367,10 +441,12 @@ class STLDoctorChecker(BaseChecker):
if register:
self.check_not_in_search(conn, modelname, expected, download = True, fail = True)
self.do_auth(conn, authstr)
- self.check_in_search(conn, modelname, expected, download = True)
+ info, stlfile = self.check_in_search(conn, modelname, expected, download = True)
+ self.check_stlinfo(info, ref_info)
self.check_listed(conn, [modelid, modelname])
else:
- self.check_in_search(conn, modelname, expected, download = True)
+ info, stlfile = self.check_in_search(conn, modelname, expected, download = True)
+ self.check_stlinfo(info, ref_info)
self.closeconn(conn)
@@ -568,8 +644,8 @@ class STLDoctorChecker(BaseChecker):
conn.write(b"search \xff\xff\xff\xff\xff0000000000000000\n")
conn.recvuntil(self.prompt)
conn.write("auth\n")
- assert_in(b"Already logged in!", conn.recvuntil(self.prompt),
- "Exploit did not set 'loggedin' variable via overflow")
+ if b"Already logged in!" not in conn.recvuntil(self.prompt):
+ raise BrokenServiceException("Exploit did not set 'loggedin' variable via overflow")
# Get private user hashes via 'list'
resp = self.do_list(conn)
diff --git a/checker/src/requirements.txt b/checker/src/requirements.txt
@@ -4,3 +4,4 @@ eventlet==0.30.2
gunicorn==20.1.0
numpy==1.20.1
Faker==8.1.4
+numpy-stl==2.16.0
diff --git a/service/src/main.c b/service/src/main.c
@@ -218,7 +218,7 @@ echo_cmd(const char *arg)
void
upload_cmd(const char *arg)
{
- char *end, *contents, *modelname;
+ char *end, *contents = NULL, *modelname = NULL;
const char *resp;
size_t len;
diff --git a/src/main.c b/src/main.c
@@ -218,7 +218,7 @@ echo_cmd(const char *arg)
void
upload_cmd(const char *arg)
{
- char *end, *contents, *modelname;
+ char *end, *contents = NULL, *modelname = NULL;
const char *resp;
size_t len;
diff --git a/tests/test.sh b/tests/test.sh
@@ -63,18 +63,18 @@ if [ "$1" == "stl-leaks" ]; then
(
echo "echo"
echo "upload"
+ echo "ASCII-testname"
cat "$TESTDATA/sample-ascii.stl" | wc -c
cat "$TESTDATA/sample-ascii.stl"
- echo "ASCII-testname"
) | checkleaks
announce "Testing BIN STL Parsing"
(
echo "echo"
echo "upload"
+ echo "BIN-testname"
cat "$TESTDATA/sample-binary.stl" | wc -c
cat "$TESTDATA/sample-binary.stl"
- echo "BIN-testname"
) | checkleaks
elif [ "$1" == "stl-upload" ]; then
@@ -90,9 +90,9 @@ elif [ "$1" == "stl-upload" ]; then
(
echo "echo"
echo "upload"
+ echo "$name"
cat "$file" | wc -c
cat "$file"
- echo "$name"
) | connect
elif [ "$1" == "auth-upload" ]; then
@@ -101,9 +101,9 @@ elif [ "$1" == "auth-upload" ]; then
echo "auth test"
echo "upload"
+ echo "testname"
cat "$TESTDATA/sample-ascii.stl" | wc -c
cat "$TESTDATA/sample-ascii.stl"
- echo "testname"
) | connect
(