aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--checker/src/checker.py440
-rwxr-xr-xservice/entrypoint.sh2
-rw-r--r--service/src/main.c10
-rw-r--r--src/main.c10
4 files changed, 226 insertions, 236 deletions
diff --git a/checker/src/checker.py b/checker/src/checker.py
index 54820ee..54e0e46 100644
--- a/checker/src/checker.py
+++ b/checker/src/checker.py
@@ -42,6 +42,7 @@ extra_models = []
for path in os.listdir(f"{script_path}/models"):
if path.endswith(".stl"):
extra_models.append(f"{script_path}/models/{path}")
+assert len(extra_models) > 0
wordlist = [w for w in open(f"{script_path}/wordlist.txt").read().split() if w != ""]
prompt = b"\r$ "
@@ -63,11 +64,11 @@ app = lambda: checker.app
async def timed(promise: Any, logger: LoggerAdapter, ctx: str) -> Any:
- logger.debug("Started: {}".format(ctx))
+ logger.debug("START: {}".format(ctx))
start = time.time()
result = await promise
end = time.time()
- logger.debug("Done: {} (took {:.3f} seconds)".format(ctx, end - start))
+ logger.debug("DONE: {} (took {:.3f} seconds)".format(ctx, end - start))
return result
@@ -80,16 +81,21 @@ class Session:
self.closed = False
async def __aenter__(self) -> "Session":
- await timed(self.prepare(), self.logger, ctx="preparing session")
+ self.logger.debug("Preparing session")
+ await self.prepare()
return self
async def __aexit__(self, *args: list[Any], **kwargs: dict[str, Any]) -> None:
- await timed(self.close(), self.logger, ctx="closing session")
+ self.logger.debug("Closing session")
+ await self.close()
async def readuntil(self, target: bytes, ctx: Optional[str] = None) -> bytes:
try:
ctxstr = f"readuntil {target!r}" if ctx is None else ctx
- return await timed(self.reader.readuntil(target), self.logger, ctx=ctxstr)
+ data = await timed(self.reader.readuntil(target), self.logger, ctx=ctxstr)
+ msg = f"read: {data[:100]!r}{'..' if len(data) > 100 else ''}"
+ self.logger.debug(msg)
+ return data
except TimeoutError:
self.logger.critical(f"Service timed out while waiting for {target!r}")
raise MumbleException("Service took too long to respond")
@@ -97,10 +103,13 @@ class Session:
async def readline(self, ctx: Optional[str] = None) -> bytes:
return await self.readuntil(b"\n", ctx=ctx)
- async def read(self, n: int, ctx: Optional[str]) -> bytes:
+ async def read(self, n: int, ctx: Optional[str] = None) -> bytes:
try:
ctxstr = f"reading {n} bytes" if ctx is None else ctx
- return await timed(self.reader.readexactly(n), self.logger, ctx=ctxstr)
+ data = await timed(self.reader.readexactly(n), self.logger, ctx=ctxstr)
+ msg = f"read: {data[:60]!r}{'..' if len(data) > 60 else ''}"
+ self.logger.debug(msg)
+ return data
except TimeoutError:
self.logger.critical(f"Service timed out while reading {n} bytes")
raise MumbleException("Service took too long to respond")
@@ -109,10 +118,8 @@ class Session:
await self.writer.drain()
def write(self, data: bytes) -> None:
- if len(data) > 100:
- self.logger.debug(f"Sending {data[:100]!r}..")
- else:
- self.logger.debug(f"Sending {data!r}")
+ msg = f"write: {data[:60]!r}{'..' if len(data) > 60 else ''}"
+ self.logger.debug(msg)
self.writer.write(data)
async def prepare(self) -> None:
@@ -153,6 +160,16 @@ def includes_any(resp: bytes, targets: list[bytes]) -> bool:
return False
+def nnt(ino: Optional[tuple[bytes, bytes]]) -> tuple[bytes, bytes]:
+ assert ino is not None
+ return ino
+
+
+def nnb(ino: Optional[bytes]) -> bytes:
+ assert ino is not None
+ return ino
+
+
def randbool() -> bool:
return rand.randint(0, 1) == 1
@@ -236,6 +253,9 @@ def assert_match(data: bytes, pattern: bytes, raiser: Any) -> bytes:
def genfile_ascii(solidname: bytes, malformed: bool = None) -> bytes:
+ # Generate a valid ascii STL file or one that is malformed in a
+ # way that it can't be interpreted by the service, but might succeed
+ # with typical stl parsing libraries (as a means to identify them)
indent = bytes([rand.choice(b"\t ") for i in range(rand.randint(1, 4))])
facet_count = rand.randint(4, 30)
@@ -245,9 +265,9 @@ def genfile_ascii(solidname: bytes, malformed: bool = None) -> bytes:
content = b"solid\n"
for fi in range(facet_count):
- # MALFORM 1: wrong keyword
+ # MALFORM 1: prefix keyword
if malformed == 1:
- content += indent * 1 + b"facet nornal "
+ content += indent * 1 + b"facet norm"
else:
content += indent * 1 + b"facet normal "
@@ -257,9 +277,9 @@ def genfile_ascii(solidname: bytes, malformed: bool = None) -> bytes:
content += " ".join([f"{v:.2f}" for v in norm]).encode() + b"\n"
- # MALFORM 2: wrong keyword case
+ # MALFORM 2: too many spaces
if malformed == 2:
- content += indent * 2 + b"outer lOop\n"
+ content += indent * 2 + b"outer loop\n"
else:
content += indent * 2 + b"outer loop\n"
@@ -274,17 +294,21 @@ def genfile_ascii(solidname: bytes, malformed: bool = None) -> bytes:
content += indent * 2 + b"endloop\n"
content += indent + b"endfacet\n"
- # MALFORM 3: no endsolid keyword
- if malformed != 3:
- if solidname != b"":
- content += b"endsolid " + solidname + b"\n"
- else:
- content += b"endsolid\n"
+ # MALFORM 3: different endsolid name
+ if malformed == 3:
+ content += b"endsolid end\n"
+ if solidname != b"":
+ content += b"endsolid " + solidname + b"\n"
+ else:
+ content += b"endsolid\n"
return content
def genfile_bin(solidname: bytes, malformed: bool = None) -> bytes:
+ # Generate a valid binary STL file or one that is malformed in a
+ # way that it can't be interpreted by the service, but might succeed
+ # with typical stl parsing libraries (as a means to identify them)
facet_count = rand.randint(4, 30)
if len(solidname) > 78:
@@ -329,16 +353,34 @@ def genfile(solidname: bytes, filetype: str, malformed: Optional[Any] = None) ->
return genfile_ascii(solidname, malformed=malformed)
elif filetype == "bin":
return genfile_bin(solidname, malformed=malformed)
- elif filetype == "garbage-tiny":
- return bytes([rand.choice(generic_alphabet) for i in range(rand.randint(3, 8))])
elif filetype == "garbage":
- return bytes(
- [rand.choice(generic_alphabet) for i in range(rand.randint(100, 300))]
- )
+ return bytes([rand.randint(0, 255) for i in range(rand.randint(100, 600))])
else:
raise InternalErrorException("Invalid file type supplied")
+def getfile(filetype: str) -> tuple[bytes, bytes]:
+ if filetype == "ascii":
+ if rand.randint(0, 20) >= 5:
+ solidname = fakeid()
+ stlfile = genfile(solidname, "ascii")
+ else:
+ model = rand.choice([v for v in extra_models if v.endswith("-ascii.stl")])
+ stlfile = open(model, "rb").read()
+ solidname = fakeid()
+ stlfile = stlfile.replace(b"OpenSCAD_Model", solidname)
+ else:
+ if rand.randint(0, 20) >= 5:
+ solidname = fakeid()
+ stlfile = genfile(solidname, "ascii")
+ else:
+ model = rand.choice([v for v in extra_models if v.endswith("-bin.stl")])
+ stlfile = open(model, "rb").read()
+ solidname = fakeid(minlen=15, maxlen=15)
+ stlfile = solidname + stlfile[15:] # replaces b"OpenSCAD Model\n"
+ return stlfile, solidname
+
+
def parse_stlinfo(stlfile: bytes) -> Any:
fakefile = BytesIO()
fakefile.write(stlfile)
@@ -388,17 +430,16 @@ async def do_auth(
await session.drain()
# Check for errors
- resp = await session.readline(ctx="reading auth response (1)")
- if b"ERR:" in resp:
+ resp = await session.readuntil(prompt, ctx="reading auth response")
+ if resp == b"ERR:":
if check:
session.logger.critical(f"Failed to login with {authstr!r}:\n{resp!r}")
raise MumbleException("Authentication not working properly")
return None
# Also check success message
- resp += await session.readuntil(prompt, ctx="reading auth response (2)")
try:
- userid = resp.split(b"Logged in with ID ")[1].split(b"!")[0]
+ userid = resp.split(b"!", 1)[0].split(b"Logged in with ID ", 1)[1]
except:
session.logger.critical(f"Login with pass {authstr!r} failed:\n{resp!r}")
raise MumbleException("Authentication not working properly")
@@ -414,15 +455,17 @@ async def do_auth(
raise MumbleException("Authentication not working properly")
return None
+ session.logger.debug(f"Logged in as user: {userid!r}")
+
return userid
async def do_list(session: Session, check: bool = True) -> Optional[bytes]:
session.write(b"list\n")
await session.drain()
- resp = await session.readuntil(prompt, ctx="reading list response")
# Check for errors
+ resp = await session.readuntil(prompt, ctx="reading list response")
if b"ERR:" in resp and b">> " not in resp:
if check:
session.logger.critical(f"Failed to list private files:\n{resp!r}")
@@ -440,40 +483,45 @@ async def do_upload(
) -> Optional[bytes]:
session.logger.debug(f"Uploading model with name {modelname!r}")
- session.write(b"upload\n")
- # enter name and check resp
+ # start upload and enter name
+ session.write(b"upload\n")
session.write(modelname + b"\n")
await session.drain()
+
+ # check for error with name
await session.readuntil(b"name: ")
resp = await session.read(4, ctx="checking for err response")
if resp == b"ERR:":
+ resp += await session.readuntil(prompt)
if check:
- resp = resp + await session.readline()
session.logger.critical(f"Failed during name check: {resp!r}")
raise MumbleException("File upload not working properly")
return None
- # enter size and check resp
+ # write size and contents in together
await session.readuntil(b"size: ")
session.write(f"{len(stlfile)}\n".encode())
+ session.write(stlfile)
await session.drain()
+
+ # check for error with size
resp = await session.read(4, ctx="checking for err response")
if resp == b"ERR:":
+ resp += await session.readuntil(prompt)
if check:
- resp = resp + await session.readline()
session.logger.critical(f"Failed during size check: {resp!r}")
raise MumbleException("File upload not working properly")
return None
+ # checker for error with file
await session.readuntil(b"listening..\n")
- session.write(stlfile)
- await session.drain()
resp = await session.readline()
if b"ERR:" in resp:
if check:
session.logger.critical(f"Failed during stl parsing: {resp!r}")
raise MumbleException("File upload not working properly")
+ await session.readuntil(prompt)
return None
# parse returned id
@@ -482,12 +530,10 @@ async def do_upload(
if modelid == b"":
raise Exception
except:
- session.logger.critical(
- f"Invalid file size during upload of {modelname!r}:\n{resp!r}"
- )
+ session.logger.critical(f"Invalid size during upload:\n{resp!r}")
raise MumbleException("File upload not working properly")
- session.logger.debug(f"Uploaded model id: {modelid!r}")
+ session.logger.debug(f"Uploaded model with id {modelid!r}")
await session.readuntil(prompt, ctx="waiting for prompt")
return modelid
@@ -504,22 +550,37 @@ async def do_search(
# get possible hashes
session.write(b"search " + modelname + b"\n")
await session.drain()
- resp = await session.readline()
- if b"ERR:" in resp:
+
+ # chck for error with search query
+ resp = await session.read(4)
+ if resp == b"ERR:":
+ resp += await session.readuntil(prompt)
if check:
session.logger.critical(
f"Failed to retrieve model {modelname!r}:\n{resp!r}"
)
raise MumbleException("File search not working properly")
return None
- resp = resp + await session.readuntil(b"> ")
- results = [l.strip() for l in resp[:-2].split(b"\n")]
- # request first result
- session.write(results[0] + b"\n")
+ # collect results
+ resp += await session.readuntil(b"quit]: ")
+ try:
+ resp = resp.split(b">")[0]
+ except:
+ session.logger.critical('Search response missing b">" delim')
+ raise MumbleException("File search not working properly")
+ results = [l.strip() for l in resp.split(b"\n") if l.strip() != b""]
+
+ # request most recent result (later in index file)
+ session.write(results[-1] + b"\n")
+ session.write(b"y\n" if download else b"n\n")
await session.drain()
- resp = await session.readline()
- if b"ERR:" in resp:
+
+ # check for error in hash provided
+ resp = await session.read(4)
+ if resp == b"ERR:":
+ # cleanup download y/n interpreted as command
+ await session.readuntil(prompt)
if check:
session.logger.critical(f"Error selecting file: {results[0]!r}")
raise MumbleException("File search not working properly")
@@ -528,9 +589,6 @@ async def do_search(
b"================== \n", ctx="reading stl info"
)
- # download if requested
- session.write(b"y\n" if download else b"n\n")
- await session.drain()
stlfile = b""
if download: # Parse file contents
await session.readuntil(b"Here you go.. (", ctx="reading stl size (1)")
@@ -555,8 +613,7 @@ async def do_search(
async def check_listed(session: Session, includes: list[bytes]) -> bytes:
- resp = await do_list(session, check=True)
- assert resp is not None
+ resp = nnb(await do_list(session, check=True))
if not includes_all(resp, includes):
session.logger.critical(f"Failed to find {includes} in listing:\n{resp!r}")
raise MumbleException("File listing not working properly")
@@ -590,8 +647,7 @@ async def check_in_search(
includes: list[bytes],
download: bool = False,
) -> tuple[bytes, bytes]:
- resp = await do_search(session, modelname, download, check=True)
- assert resp is not None
+ resp = nnt(await do_search(session, modelname, download, check=True))
if not includes_all(resp[0] + resp[1], includes):
session.logger.critical(
f"Retrieved info for {modelname!r} is missing {includes}: {resp[0]+resp[1]!r}"
@@ -708,64 +764,24 @@ def check_stlinfo(
# TEST METHODS #
-async def test_good_upload(
- di: DependencyInjector, filetype: str, register: bool
-) -> None:
- solidname, modelname, authstr = fakeids(3)
- stlfile = genfile(solidname, filetype)
- ref_info = parse_stlinfo(stlfile)
+async def test_good_upload(di: DependencyInjector, filetype: str) -> None:
+ modelname = fakeid()
+ stlfile, solidname = getfile(filetype)
+ ref = parse_stlinfo(stlfile)
- # Create new session, register and upload file
+ # Upload file, get it with search, verify returned info
session = await di.get(Session)
- if register:
- await do_auth(session, authstr, check=True, newuser=True)
- modelid = await do_upload(session, modelname, stlfile, check=True)
- assert modelid is not None
- check_hash(modelid)
+ modelid = nnb(await do_upload(session, modelname, stlfile, check=True))
expected = [modelname, solidname, stlfile, modelid]
- info, stlfile = await check_in_search(session, modelname, expected, download=True)
+ info, stl = await check_in_search(session, modelname, expected, download=True)
check_stlinfo(
session.logger,
info,
- ref_info,
+ ref,
ref_modelname=modelname,
ref_modelid=modelid,
ref_solidname=solidname,
)
- if register:
- await check_listed(session, [modelname, modelid + b"-"])
-
- # Try getting file from a new session
- session = await di.get(Session)
- if register:
- await check_not_in_search(
- session, modelname, expected, download=True, fail=True
- )
- await do_auth(session, authstr, check=True, newuser=False)
- info, stlfile = await check_in_search(
- session, modelname, expected, download=True
- )
- check_stlinfo(
- session.logger,
- info,
- ref_info,
- ref_modelname=modelname,
- ref_modelid=modelid,
- ref_solidname=solidname,
- )
- await check_listed(session, [modelname, modelid + b"-"])
- else:
- info, stlfile = await check_in_search(
- session, modelname, expected, download=True
- )
- check_stlinfo(
- session.logger,
- info,
- ref_info,
- ref_modelname=modelname,
- ref_modelid=modelid,
- ref_solidname=solidname,
- )
async def test_bad_upload(di: DependencyInjector, filetype: str, variant: int) -> None:
@@ -780,19 +796,17 @@ async def test_bad_upload(di: DependencyInjector, filetype: str, variant: int) -
raise MumbleException("Upload validation not working properly")
-async def test_search(di: DependencyInjector, registered: bool = False) -> None:
- solidname, modelname, authstr = fakeids(3)
-
- # Ensure searching for a file that doesnt exist causes an error
+async def test_hash_collision(di: DependencyInjector) -> None:
+ # See if using a random string we hit a hash collision for search / auth
session = await di.get(Session)
- if registered:
- await do_auth(session, authstr, check=True, newuser=True)
- resp = await do_search(session, modelname, download=False, check=False)
- if resp is not None:
- session.logger.critical(
- f"Search for file that shouldn't exist succeeded:\n{resp[0]+resp[1]!r}"
- )
- raise MumbleException("File search not working properly")
+ sresp = await do_search(session, fakeid(), download=False, check=False)
+ if sresp is not None:
+ session.logger.critical("File search succeeded on random file")
+ raise MumbleException("Hash function not working properly")
+ aresp = await do_auth(session, fakeid(), check=False, newuser=False)
+ if aresp is not None:
+ session.logger.critical("Auth succeeded for user with random str")
+ raise MumbleException("Hash function not working properly")
# CHECKER METHODS #
@@ -801,13 +815,12 @@ async def test_search(di: DependencyInjector, registered: bool = False) -> None:
@checker.putflag(0)
async def putflag_guest(task: PutflagCheckerTaskMessage, di: DependencyInjector) -> str:
modelname = fakeid()
+ stlfile = genfile(task.flag.encode(), "ascii")
db = await di.get(ChainDB)
# Generate a file with flag in solidname and upload it (unregistered, ascii)
session = await di.get(Session)
- stlfile = genfile(task.flag.encode(), "ascii")
- modelid = await do_upload(session, modelname, stlfile, check=True)
- assert modelid is not None
+ modelid = nnb(await do_upload(session, modelname, stlfile, check=True))
await db.set("info", (modelname, modelid))
return "Model {}.. is kinda sus".format(modelid[:10].decode())
@@ -823,219 +836,171 @@ async def putflag_private(
# Generate a file with flag in solidname and upload it (registered, bin)
session = await di.get(Session)
- userid = await do_auth(session, authstr, check=True, newuser=True)
- assert userid is not None
- modelid = await do_upload(session, modelname, stlfile, check=True)
- assert modelid is not None
+ userid = nnb(await do_auth(session, authstr, check=True, newuser=True))
+ modelid = nnb(await do_upload(session, modelname, stlfile, check=True))
await db.set("info", (modelname, modelid, authstr))
return "User {}.. is kinda sus".format(userid[:10].decode())
@checker.getflag(0)
-async def getflag_guest(
+async def getflag_unregistered(
task: GetflagCheckerTaskMessage, di: DependencyInjector
) -> None:
db = await di.get(ChainDB)
modelname, modelid = await getdb(db, "info")
- # Retrieve flag file info via search and ensure flag's included
+ # Search for flag file and verify flag is included
session = await di.get(Session)
- resp = await do_search(session, modelname, download=True, check=True)
- assert resp is not None
- assert_in(task.flag.encode(), resp[0], "Flag is missing from stl info")
- assert_in(task.flag.encode(), resp[1], "Flag is missing from stl file")
+ sresp = nnt(await do_search(session, modelname, download=True, check=True))
+ assert_in(task.flag.encode(), sresp[0] + sresp[1], "Failed to retrieve flag")
@checker.getflag(1)
-async def getflag_private(
+async def getflag_registered(
task: GetflagCheckerTaskMessage, di: DependencyInjector
) -> None:
db = await di.get(ChainDB)
modelname, modelid, authstr = await getdb(db, "info")
- # Retrieve private flag file info via search / list and ensure flag's included
+ # Authenticate, get flag via search and list
session = await di.get(Session)
await do_auth(session, authstr, check=True, newuser=False)
- search_resp = await do_search(session, modelname, download=True, check=True)
- assert search_resp is not None
- assert_in(task.flag.encode(), search_resp[0], "Flag is missing from stl info")
- assert_in(task.flag.encode(), search_resp[1], "Flag is missing from stl file")
- list_resp = await do_list(session, check=True)
- assert list_resp is not None
- assert_in(task.flag.encode(), list_resp, "Flag is missing from list")
+ sresp = nnt(await do_search(session, modelname, download=True, check=True))
+ assert_in(task.flag.encode(), sresp[0] + sresp[1], "Failed to retrieve flag")
+ lresp = nnb(await do_list(session, check=True))
+ assert_in(task.flag.encode(), lresp, "Failed to retrieve flag")
-@checker.putnoise(0, 1)
-async def putnoise_guest(
+@checker.putnoise(0)
+async def putnoise_unregistered(
task: PutnoiseCheckerTaskMessage, di: DependencyInjector
) -> None:
modelname, solidname = fakeids(2)
+ stlfile, solidname = getfile("ascii" if randbool() else "bin")
db = await di.get(ChainDB)
- # Generate a random file and upload it (unregistered, bin / ascii)
+ # Upload file for later checking
session = await di.get(Session)
- stlfile = genfile(solidname, "ascii" if task.variant_id == 0 else "bin")
modelid = await do_upload(session, modelname, stlfile, check=True)
await db.set("info", (modelid, modelname, solidname, stlfile))
-@checker.putnoise(2, 3)
-async def putnoise_priv(
+@checker.putnoise(1)
+async def putnoise_registered(
task: PutnoiseCheckerTaskMessage, di: DependencyInjector
) -> None:
modelname, solidname, authstr = fakeids(3)
+ stlfile, solidname = getfile("ascii" if randbool() else "bin")
db = await di.get(ChainDB)
- # Generate a random file and upload it (registered, bin / ascii)
+ # Upload private file for later checking
session = await di.get(Session)
- stlfile = genfile(solidname, "ascii" if task.variant_id == 0 else "bin")
await do_auth(session, authstr, check=True, newuser=True)
modelid = await do_upload(session, modelname, stlfile, check=True)
await db.set("info", (modelid, modelname, solidname, stlfile, authstr))
-@checker.getnoise(0, 1)
-async def getnoise_guest(
+@checker.getnoise(0)
+async def getnoise_unregistered(
task: GetnoiseCheckerTaskMessage, di: DependencyInjector
) -> None:
db = await di.get(ChainDB)
modelid, modelname, solidname, stlfile = await getdb(db, "info")
+ ref = parse_stlinfo(stlfile)
- # Retrieve noise file by name via search
session = await di.get(Session)
- await check_in_search(
- session,
- modelname,
- [modelname, solidname, stlfile, modelid],
- download=True,
+ # check that search works on persisted file
+ expected = [modelname, solidname, stlfile, modelid]
+ info, stl = await check_in_search(session, modelname, expected, download=True)
+ # check that persisted file info is still valid
+ check_stlinfo(
+ session.logger,
+ info,
+ ref,
+ ref_modelname=modelname,
+ ref_modelid=modelid,
+ ref_solidname=solidname,
)
-@checker.getnoise(2, 3)
-async def getnoise_priv(
+@checker.getnoise(1)
+async def getnoise_registered(
task: GetnoiseCheckerTaskMessage, di: DependencyInjector
) -> None:
db = await di.get(ChainDB)
modelid, modelname, solidname, stlfile, authstr = await getdb(db, "info")
+ ref = parse_stlinfo(stlfile)
- # Retrieve noise file by name via search and search (registered)
session = await di.get(Session)
+ # check that auth works and tells us we are logging in again
await do_auth(session, authstr, check=True, newuser=False)
- await check_in_search(
- session,
- modelname,
- [modelname, solidname, stlfile, modelid],
- download=True,
+ # check that search works on persisted file
+ expected = [modelname, solidname, stlfile, modelid]
+ info, stl = await check_in_search(session, modelname, expected, download=True)
+ # check that persisted file info is still valid
+ check_stlinfo(
+ session.logger,
+ info,
+ ref,
+ ref_modelname=modelname,
+ ref_modelid=modelid,
+ ref_solidname=solidname,
)
- await check_listed(session, [modelname, solidname, modelid])
+ # check that list works on persisted file
+ await check_listed(session, [modelname, modelid + b"-", solidname])
@checker.havoc(0)
-async def havoc_good_upload_guest_ascii(di: DependencyInjector) -> None:
- await test_good_upload(di, filetype="ascii", register=False)
+async def havoc_good_upload_ascii(di: DependencyInjector) -> None:
+ await test_good_upload(di, "ascii")
@checker.havoc(1)
-async def havoc_good_upload_guest_bin(di: DependencyInjector) -> None:
- await test_good_upload(di, filetype="bin", register=False)
-
-
-@checker.havoc(2)
-async def havoc_good_upload_priv_ascii(di: DependencyInjector) -> None:
- await test_good_upload(di, filetype="ascii", register=True)
-
-
-@checker.havoc(3)
-async def havoc_good_upload_priv_bin(di: DependencyInjector) -> None:
- await test_good_upload(di, filetype="bin", register=True)
-
-
-@checker.havoc(4)
async def havoc_bad_upload_ascii_v1(di: DependencyInjector) -> None:
await test_bad_upload(di, "ascii", 1)
-@checker.havoc(5)
+@checker.havoc(2)
async def havoc_bad_upload_ascii_v2(di: DependencyInjector) -> None:
await test_bad_upload(di, "ascii", 2)
-@checker.havoc(6)
+@checker.havoc(3)
async def havoc_bad_upload_ascii_v3(di: DependencyInjector) -> None:
await test_bad_upload(di, "ascii", 3)
-@checker.havoc(7)
+@checker.havoc(4)
+async def havoc_good_upload_bin(di: DependencyInjector) -> None:
+ await test_good_upload(di, "bin")
+
+
+@checker.havoc(5)
async def havoc_bad_upload_bin_v1(di: DependencyInjector) -> None:
await test_bad_upload(di, "bin", 1)
-@checker.havoc(8)
+@checker.havoc(6)
async def havoc_bad_upload_bin_v2(di: DependencyInjector) -> None:
await test_bad_upload(di, "bin", 2)
-@checker.havoc(9)
+@checker.havoc(7)
async def havoc_bad_upload_bin_v3(di: DependencyInjector) -> None:
await test_bad_upload(di, "bin", 3)
-@checker.havoc(10)
+@checker.havoc(8)
async def havoc_bad_upload_garbage(di: DependencyInjector) -> None:
await test_bad_upload(di, "garbage", 1)
-@checker.havoc(11)
-async def havoc_bad_upload_garbage_tiny(di: DependencyInjector) -> None:
- await test_bad_upload(di, "garbage-tiny", 1)
-
-
-@checker.havoc(12)
-async def havoc_test_search_guest(di: DependencyInjector) -> None:
- await test_search(di, registered=False)
-
-
-@checker.havoc(13)
-async def havoc_test_search_priv(di: DependencyInjector) -> None:
- await test_search(di, registered=True)
-
-
-@checker.havoc(14)
-async def havoc_test_list_guest(di: DependencyInjector) -> None:
- # Ensure that list does not work for unregistered users
- session = await di.get(Session)
- resp = await do_list(session, check=False)
- if resp is not None:
- session.logger.critical("Unregistered user can run list without ERR!")
- raise MumbleException("User authentication not working properly")
-
-
-@checker.havoc(15)
-async def havoc_fluff_upload(di: DependencyInjector) -> None:
- if len(extra_models) == 0:
- return
- model = rand.choice(extra_models)
- stlfile = open(model, "rb").read()
- modelname = fakeid()
- if model.endswith("-bin.stl"):
- solidname = fakeid(minlen=15, maxlen=15)
- stlfile = solidname + stlfile[15:] # replaces "OpenSCAD Model\n"
- else:
- solidname = fakeid()
- stlfile = stlfile.replace(b"OpenSCAD_Model", solidname)
-
- # Simple Upload
- session = await di.get(Session)
- if randbool():
- await do_auth(session, fakeid(), check=True, newuser=True)
- modelid = await do_upload(session, modelname, stlfile, check=True)
- assert modelid is not None
- await check_in_search(
- session, modelname, [modelname, solidname, modelid, stlfile], download=True
- )
+@checker.havoc(9)
+async def havoc_hash_collision(di: DependencyInjector) -> None:
+ await test_hash_collision(di)
@checker.exploit(0)
@@ -1099,6 +1064,7 @@ async def exploit_hash_overflow(
task: ExploitCheckerTaskMessage, di: DependencyInjector
) -> None:
searcher = await di.get(FlagSearcher)
+ logger = await di.get(LoggerAdapter)
assert task.attack_info is not None
target_prefix = task.attack_info.split()[1][:-2].encode()
@@ -1124,6 +1090,9 @@ async def exploit_hash_overflow(
if flag := searcher.search_flag(resp):
return flag
+ logger.debug(f"Searching for user with prefix: {target_prefix!r}")
+ logger.debug(f"Possible users: {users!r}")
+
# Login as each private user
for userhash in users:
if not userhash.startswith(target_prefix):
@@ -1134,6 +1103,7 @@ async def exploit_hash_overflow(
# Authenticate and check if the user is new
session = await di.get(Session)
+ logger.debug(f"Trying to login as user: {userhash!r}")
if not await do_auth(session, authstr, check=False, newuser=False):
await session.exit()
# We dont raise an exception, because it could be that user dir was cleaned
diff --git a/service/entrypoint.sh b/service/entrypoint.sh
index 8b6f3e0..61b85e6 100755
--- a/service/entrypoint.sh
+++ b/service/entrypoint.sh
@@ -12,5 +12,5 @@ while [ 1 ]; do
done &
CMD="ncat --keep-open --listen -p 9000 --max-conns 4000 \
---no-shutdown --wait 10s --idle-timeout 180s --exec /service/build/stldoctor"
+--no-shutdown --wait 10s --exec /service/build/stldoctor"
su -s /bin/sh -c "$CMD" service
diff --git a/service/src/main.c b/service/src/main.c
index dd8bca9..c571df9 100644
--- a/service/src/main.c
+++ b/service/src/main.c
@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <time.h>
#include <errno.h>
+#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>
@@ -454,6 +455,12 @@ cleanexit()
free(resultdir);
}
+void
+timeout()
+{
+ die("time's up!\n");
+}
+
int
main()
{
@@ -461,6 +468,9 @@ main()
int exit, i, cmdlen;
char *cp, *arg;
+ signal(SIGALRM, timeout);
+ alarm(3);
+
if (!(envstr = getenv("RESULTDIR")))
die("RESULTDIR not defined\n");
diff --git a/src/main.c b/src/main.c
index ff04849..cf4c3dc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <time.h>
#include <errno.h>
+#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>
@@ -458,6 +459,12 @@ cleanexit()
free(resultdir);
}
+void
+timeout()
+{
+ die("time's up!\n");
+}
+
int
main()
{
@@ -465,6 +472,9 @@ main()
int exit, i, cmdlen;
char *cp, *arg;
+ signal(SIGALRM, timeout);
+ alarm(180);
+
if (!(envstr = getenv("RESULTDIR")))
die("RESULTDIR not defined\n");