commit d1d4462f5661e0d15176375ec297b3c59d0896c3
parent 7cc88b34e67b3d35ca10bcbf8b393dbc2713b63e
Author: Louis Burda <quent.burda@gmail.com>
Date: Thu, 24 Jun 2021 19:34:08 +0200
add more havocs to test stl parsing
Diffstat:
7 files changed, 155 insertions(+), 112 deletions(-)
diff --git a/checker/src/checker.py b/checker/src/checker.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
from enochecker import BaseChecker, BrokenServiceException, EnoException, run
from enochecker.utils import SimpleSocket, assert_equals, assert_in
-import os, random, string, struct, subprocess, logging, selectors, time, socket
+import math, os, random, string, struct, subprocess, logging, selectors, time, socket
import numpy as np
logging.getLogger("faker").setLevel(logging.WARNING)
@@ -28,16 +28,18 @@ signal.signal(signal.SIGALRM, handler)
evil_file = b"""
solid test\xff
-facet normal 0 0 1.0
- outer loop
- vertex 1 0 0
- vertex 1 1 0
- vertex 0 1 0
- endloop
- endfacet
-endsolid
+ facet normal 0 0 1.0
+ outer loop
+ vertex 1 0 0
+ vertex 1 1 0
+ vertex 0 1 0
+ endloop
+ endfacet
+endsolid test\xff
"""
+generic_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz0123456789-+.!"
+
def ensure_bytes(v):
if type(v) == bytes:
return v
@@ -52,7 +54,7 @@ class STLDoctorChecker(BaseChecker):
flag_variants = 2
noise_variants = 2
- havoc_variants = 4
+ havoc_variants = 8
exploit_variants = 2
prompt = b"$ "
@@ -71,9 +73,8 @@ class STLDoctorChecker(BaseChecker):
def fakeid(self):
fake = Faker(["en_US"])
- allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz0123456789-+.!"
- idstr = "".join([c for c in fake.name().replace(' ','') if c in allowed][:12]).ljust(10, '.')
- idstr += "".join([rand.choice(allowed) for i in range(8)])
+ idstr = "".join([c for c in fake.name().replace(' ','') if c in generic_alphabet][:12]).ljust(10, '.')
+ idstr += "".join([rand.choice(generic_alphabet) for i in range(8)])
return idstr
def havocid(self):
@@ -110,34 +111,46 @@ class STLDoctorChecker(BaseChecker):
def reverse_hash(self, hashstr):
return subprocess.check_output([os.getenv("REVHASH_PATH"), hashstr])[:-1]
- def genfile_ascii(self, solidname):
+ def genfile_ascii(self, solidname, malformed=False):
solidname = ensure_bytes(solidname)
+ randchoice = rand.randint(0,2)
if len(solidname) != 0:
content = b"solid " + solidname + b"\n"
else:
content = b"solid\n"
facet_count = rand.randint(4, 30)
+ indent = b" " * rand.randint(1, 4)
for fi in range(facet_count):
- content += b"facet normal "
+ if malformed and randchoice == 0: # malformed by wrong keyword
+ content += indent * 1 + b"facet nornal "
+ else:
+ content += indent * 1 + b"facet normal "
vs = [[rand.random() for i in range(3)] for k in range(3)]
norm = np.cross(np.subtract(vs[1], vs[0]), np.subtract(vs[2],vs[0]))
norm = norm / np.linalg.norm(norm)
content += " ".join([f"{v:.2f}" for v in norm]).encode() + b"\n"
- content += b"outer loop\n"
+ if malformed and randchoice == 1: # malformed wrong keyword case
+ content += indent * 2 + b"outer lOop\n"
+ else:
+ content += indent * 2 + b"outer loop\n"
for i in range(3):
- content += b"vertex " + " ".join([f"{v:.2f}" for v in vs[i]]).encode() + b"\n"
- content += b"endloop\n"
- content += b"endfacet\n"
- if solidname != b"":
- content += b"endsolid " + solidname + b"\n"
+ content += indent * 3 + b"vertex " + " ".join([f"{v:.2f}" for v in vs[i]]).encode() + b"\n"
+ content += indent * 2 + b"endloop\n"
+ content += indent + b"endfacet\n"
+ if malformed and randchoice == 2:
+ content += b"" # malformed since no endsolid
else:
- content += b"endsolid\n"
+ if solidname != b"":
+ content += b"endsolid " + solidname + b"\n"
+ else:
+ content += b"endsolid\n"
return content
- def genfile_bin(self, solidname):
+ def genfile_bin(self, solidname, malformed=False):
solidname = ensure_bytes(solidname)
+ randchoice = rand.randint(0, 3)
if len(solidname) > 78:
raise EnoException("Solidname to embed in header is larger than header itself")
@@ -146,23 +159,36 @@ class STLDoctorChecker(BaseChecker):
else:
content = b"#" + b"\x00" * 79
facet_count = rand.randint(4, 30)
- content += struct.pack("<I", facet_count)
+ if malformed and randchoice == 0: # malform by specifying more facets than are in the file
+ content += struct.pack("<I", facet_count + rand.randint(3, 7))
+ else:
+ content += struct.pack("<I", facet_count)
for fi in range(facet_count):
vs = [[rand.random() for i in range(3)] for k in range(3)]
norm = np.cross(np.subtract(vs[1], vs[0]), np.subtract(vs[2],vs[0]))
+ if malformed and randchoice == 2: # malform by setting invalid float in norm
+ norm[rand.randint(0,2)] = math.inf
+ elif malformed and randchoice == 3: # same malformation, but in vec
+ vs[rand.randint(0,2)][rand.randint(0,2)] = math.inf
for i in range(3):
content += struct.pack("<f", norm[i])
for k in range(3):
for i in range(3):
content += struct.pack("<f", vs[k][i])
content += b"\x00\x00"
+ if malformed and randchoice == 1: # malform by adding extra data to the end of the file
+ content += bytes([rand.randint(0, 255) for i in range(30)])
return content
- def genfile(self, filetype, solidname):
+ def genfile(self, filetype, solidname, malformed=False):
if filetype == "ascii":
- return self.genfile_ascii(solidname)
+ return self.genfile_ascii(solidname, malformed=malformed)
elif filetype == "bin":
- return self.genfile_bin(solidname)
+ 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()
+ elif filetype == "garbage":
+ return ("".join([rand.choice(generic_alphabet) for i in range(rand.randint(100, 300))])).encode()
else:
raise EnoException("Invalid file type supplied");
@@ -258,6 +284,22 @@ class STLDoctorChecker(BaseChecker):
self.check_listed(conn, modelid)
self.closeconn(conn)
+ def malformed_upload(self, filetype):
+ conn = self.openconn()
+ solidname = self.fakeid()
+ modelname = self.fakeid()
+ contents = self.genfile(filetype, solidname, malformed = True)
+ conn.write("upload\n")
+ conn.write(f"{len(contents)}\n")
+ conn.write(contents)
+ conn.write(modelname + "\n")
+ if filetype == "garbage-tiny":
+ conn.recvuntil("ERR: File too small")
+ else:
+ conn.recvuntil("ERR:")
+ conn.recvuntil(self.prompt)
+ self.closeconn(conn)
+
def putflag(self): # type: () -> None
if self.variant_id == 0:
conn = self.openconn()
@@ -337,6 +379,14 @@ class STLDoctorChecker(BaseChecker):
self.havoc_upload('ascii', True)
elif self.variant_id == 3:
self.havoc_upload('bin', True)
+ elif self.variant_id == 4:
+ self.malformed_upload('ascii')
+ elif self.variant_id == 5:
+ self.malformed_upload('bin')
+ elif self.variant_id == 6:
+ self.malformed_upload('garbage')
+ elif self.variant_id == 7:
+ self.malformed_upload('garbage-tiny')
else:
raise EnoException(f"Invalid havoc variant ({self.variant_id}) provided")
@@ -372,7 +422,6 @@ class STLDoctorChecker(BaseChecker):
raise BrokenServiceException("Download size is not a valid integer")
resp += conn.recvn(size)
resp += conn.recvuntil("? ")
- self.debug(resp)
found = self.search_flag_bytes(resp)
if found is not None or i == len(filelist) - 1:
break
diff --git a/checker/src/requirements.txt b/checker/src/requirements.txt
@@ -1,28 +1,6 @@
-certifi==2020.12.5
-chardet==4.0.0
-click==7.1.2
-dnspython==1.16.0
-# enochecker==0.4.2
-# git+https://github.com/enowars/enochecker@37981175f3125bd552c3c351494186fe9ce35e0b
+
git+https://github.com/Sinitax/enochecker@f04cab0fd57fbc927809e88c97a1dd37579089ee
-enochecker-cli==0.7.0
-enochecker-core==0.10.0
eventlet==0.30.2
-Flask==1.1.2
-greenlet==1.0.0
gunicorn==20.1.0
-idna==2.10
-itsdangerous==1.1.0
-Jinja2==2.11.3
-jsons==1.4.2
-MarkupSafe==1.1.1
-pycryptodome==3.10.1
-pymongo==3.11.3
-requests==2.25.1
-six==1.15.0
-typish==1.9.2
-urllib3==1.26.5
-Werkzeug==1.0.1
numpy==1.20.1
Faker==8.1.4
-pwntools==4.5.0
diff --git a/service/docker-compose.yml b/service/docker-compose.yml
@@ -1,7 +1,7 @@
version: '2.1'
services:
- printdoc:
+ stldoctor:
ulimits:
core:
hard: 0
diff --git a/service/src/main.c b/service/src/main.c
@@ -108,12 +108,12 @@ handle_download(const char *scandir)
infopath = aprintf("%s/%s", scandir, "info");
if (!(f = fopen(infopath, "r"))) {
- printf("Selected result is missing!\n");
+ printf("ERR: Selected result is missing!\n");
goto cleanup;
}
free_info(&cached);
if (load_info(&cached, f) != OK) {
- printf("Failed to parse info file!\n");
+ printf("ERR: Failed to parse info file!\n");
goto cleanup;
}
fclose(f);
@@ -124,14 +124,14 @@ handle_download(const char *scandir)
if (strchr(ask("Download the model? "), 'y')) {
modelpath = aprintf("%s/%s", scandir, "model");
if (!(f = fopen(modelpath, "r"))) {
- printf("Failed to access file!\n");
+ printf("ERR: Failed to access file!\n");
goto cleanup;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size > MAXFILESIZE) {
- printf("File is too large to send!\n");
+ printf("ERR: File is too large!\n");
goto cleanup;
}
printf("Here you go.. (%liB)\n", size);
@@ -194,28 +194,28 @@ echo_cmd(const char *arg)
void
upload_cmd(const char *arg)
{
- const char *bufp;
+ const char *resp;
char *end, *contents;
size_t len;
- bufp = ask("How large is your file? ");
- len = strtoul(bufp, &end, 10);
+ resp = ask("How large is your file? ");
+ len = strtoul(resp, &end, 10);
if (len <= 0 || len >= MAXFILESIZE || *end) {
- printf("Invalid file length!\n");
+ printf("ERR: Invalid file length!\n");
return;
}
printf("Ok! Im listening..\n");
contents = checkp(malloc(len + 1));
if (fread(contents, 1, len, stdin) != len) {
- printf("Hm, I'm missing some bytes.. try again!\n");
+ printf("ERR: Not enough data received!\n");
goto cleanup;
}
contents[len] = '\0';
if ((cached.valid = parse_file(&cached, contents, len))) {
if (save_submission(&cached, contents, len) != OK)
- printf("Failed to save your submission!\n");
+ printf("ERR: Failed to save your submission!\n");
else
printf("Your file was saved with ID %s!\n", cached.hash);
}
@@ -235,7 +235,7 @@ search_cmd(const char *arg)
if (arg && !strcmp(arg, "last")) {
if (!cached.valid) {
- printf("No cached info report available\n");
+ printf("ERR: No cached info report available\n");
return;
}
hash = cached.hash;
@@ -244,7 +244,7 @@ search_cmd(const char *arg)
}
if (!(d = opendir(resultdir))) {
- printf("Unable to access upload directory!\n");
+ printf("ERR: Unable to access upload directory!\n");
return;
}
@@ -263,7 +263,7 @@ search_cmd(const char *arg)
closedir(d);
if (pathc == 0) {
- printf("Sorry, couldnt find a matching scan result!\n");
+ printf("ERR: Couldn't find a matching scan result!\n");
goto cleanup;
}
@@ -272,7 +272,7 @@ search_cmd(const char *arg)
if (strchr(resp, 'q')) break;
which = strtoul(resp, &end, 10);
if (which >= pathc || which < 0 || *end) {
- printf("Invalid index!\n");
+ printf("ERR: Invalid index!\n");
goto cleanup;
}
@@ -301,7 +301,7 @@ list_cmd(const char *arg)
DIR *d;
if (!loggedin) {
- printf("Not logged in!\n");
+ printf("ERR: Not logged in!\n");
return;
}
@@ -315,7 +315,7 @@ list_cmd(const char *arg)
if (load_info(&info, f) == OK)
print_info(&info);
else
- printf("Failed to read saved file info!\n");
+ printf("ERR: Failed to read saved file info!\n");
fclose(f);
}
free(path);
@@ -331,7 +331,7 @@ auth_cmd(const char *arg)
int ret;
if (loggedin) {
- printf("Already logged in!\n");
+ printf("ERR: Already logged in!\n");
return;
}
@@ -343,7 +343,7 @@ auth_cmd(const char *arg)
} else if (ret && errno == EEXIST) {
printf("Success!\nWelcome back!\n");
} else {
- printf("Auth failed!\n");
+ printf("ERR: Auth failed!\n");
return;
}
@@ -369,7 +369,7 @@ main()
int exit, i, cmdlen;
if (!(envstr = getenv("RESULTDIR"))) {
- printf("RESULTDIR not defined\n");
+ printf("ERR: RESULTDIR not defined\n");
return 1;
}
diff --git a/service/src/stlfile.c b/service/src/stlfile.c
@@ -203,6 +203,9 @@ parse_file_ascii(struct parseinfo *info, char *buf, size_t len)
if (states.count)
PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, bp);
+ bp = skipws(bp);
+ if (*bp) PARSE_FAIL("Extraneous data at end of file\n");
+
stack_free(&states);
return OK;
@@ -215,7 +218,7 @@ int
parse_file_bin(struct parseinfo *info, char *buf, size_t len)
{
char *bp, *end = buf + len;
- int i, k, m;
+ int i, k;
float v;
info->type = TYPE_BIN;
@@ -225,11 +228,13 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len)
memcpy(info->header, buf, 80);
- if (strlen(buf + 1))
+ if (*buf == '#' && strlen(buf + 1))
info->solidname = checkp(strdup(buf + 1));
bp = buf + 80;
+
info->loopcount = le32toh(*(uint32_t*)bp);
+ bp += 4;
if (!info->loopcount) {
memset(info->bbmax, 0, sizeof(float) * 3);
@@ -245,17 +250,20 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len)
for (i = 0; i < info->loopcount; i++) {
if (bp + 50 > end)
PARSE_FAIL("Truncated data! (loops missing)\n");
- bp += 12;
- for (k = 0; k < 3; k++, bp += 12) {
- for (m = 0; m < 3; m++) {
- v = fle32toh(*(float*)(bp + 4 * m));
- info->bbmin[m] = MIN(info->bbmin[m], v);
- info->bbmax[m] = MAX(info->bbmax[m], v);
+ for (k = 0; k < 12; k++, bp += 4) {
+ v = fle32toh(*(float*)bp);
+ if (v == INFINITY || v == NAN)
+ PARSE_FAIL("Encountered invalid float\n");
+ if (k >= 3) {
+ info->bbmin[k % 3] = MIN(info->bbmin[k % 3], v);
+ info->bbmax[k % 3] = MAX(info->bbmax[k % 3], v);
}
}
bp += 2;
}
+ if (bp != end) PARSE_FAIL("Extraneous data at end of file\n");
+
return OK;
fail:
@@ -271,8 +279,8 @@ parse_file(struct parseinfo *info, char *buf, size_t len)
if (info->valid) free_info(info);
- if (len < 7) {
- printf("File too small!\n");
+ if (len < 10) {
+ printf("ERR: File too small!\n");
return FAIL;
}
@@ -289,7 +297,7 @@ parse_file(struct parseinfo *info, char *buf, size_t len)
if (!info->modelname) {
resp = ask("Please enter your model name: ");
if (strlen(resp) < 4) {
- printf("Model name is too short!\n");
+ printf("ERR: Model name is too short!\n");
return FAIL;
}
info->modelname = checkp(strdup(resp));
diff --git a/src/main.c b/src/main.c
@@ -108,12 +108,12 @@ handle_download(const char *scandir)
infopath = aprintf("%s/%s", scandir, "info");
if (!(f = fopen(infopath, "r"))) {
- fprintf(stderr, "Selected result is missing!\n");
+ fprintf(stderr, "ERR: Selected result is missing!\n");
goto cleanup;
}
free_info(&cached);
if (load_info(&cached, f) != OK) {
- fprintf(stderr, "Failed to parse info file!\n");
+ fprintf(stderr, "ERR: Failed to parse info file!\n");
goto cleanup;
}
fclose(f);
@@ -124,14 +124,14 @@ handle_download(const char *scandir)
if (strchr(ask("Download the model? "), 'y')) {
modelpath = aprintf("%s/%s", scandir, "model");
if (!(f = fopen(modelpath, "r"))) {
- fprintf(stderr, "Failed to access file!\n");
+ fprintf(stderr, "ERR: Failed to access file!\n");
goto cleanup;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size > MAXFILESIZE) {
- fprintf(stderr, "File is too large to send!\n");
+ fprintf(stderr, "ERR: File is too large!\n");
goto cleanup;
}
printf("Here you go.. (%liB)\n", size);
@@ -194,28 +194,28 @@ echo_cmd(const char *arg)
void
upload_cmd(const char *arg)
{
- const char *bufp;
+ const char *resp;
char *end, *contents;
size_t len;
- bufp = ask("How large is your file? ");
- len = strtoul(bufp, &end, 10);
+ resp = ask("How large is your file? ");
+ len = strtoul(resp, &end, 10);
if (len <= 0 || len >= MAXFILESIZE || *end) {
- fprintf(stderr, "Invalid file length!\n");
+ fprintf(stderr, "ERR: Invalid file length!\n");
return;
}
printf("Ok! Im listening..\n");
contents = checkp(malloc(len + 1));
if (fread(contents, 1, len, stdin) != len) {
- fprintf(stderr, "Hm, I'm missing some bytes.. try again!\n");
+ fprintf(stderr, "ERR: Not enough data received!\n");
goto cleanup;
}
contents[len] = '\0';
if ((cached.valid = parse_file(&cached, contents, len))) {
if (save_submission(&cached, contents, len) != OK)
- fprintf(stderr, "Failed to save your submission!\n");
+ fprintf(stderr, "ERR: Failed to save your submission!\n");
else
printf("Your file was saved with ID %s!\n", cached.hash);
}
@@ -235,7 +235,7 @@ search_cmd(const char *arg)
if (arg && !strcmp(arg, "last")) {
if (!cached.valid) {
- fprintf(stderr, "No cached info report available\n");
+ fprintf(stderr, "ERR: No cached info report available\n");
return;
}
hash = cached.hash;
@@ -244,7 +244,7 @@ search_cmd(const char *arg)
}
if (!(d = opendir(resultdir))) {
- fprintf(stderr, "Unable to access upload directory!\n");
+ fprintf(stderr, "ERR: Unable to access upload directory!\n");
return;
}
@@ -263,7 +263,7 @@ search_cmd(const char *arg)
closedir(d);
if (pathc == 0) {
- fprintf(stderr, "Sorry, couldnt find a matching scan result!\n");
+ fprintf(stderr, "ERR: Couldn't find a matching scan result!\n");
goto cleanup;
}
@@ -272,7 +272,7 @@ search_cmd(const char *arg)
if (strchr(resp, 'q')) break;
which = strtoul(resp, &end, 10);
if (which >= pathc || which < 0 || *end) {
- fprintf(stderr, "Invalid index!\n");
+ fprintf(stderr, "ERR: Invalid index!\n");
goto cleanup;
}
@@ -301,7 +301,7 @@ list_cmd(const char *arg)
DIR *d;
if (!loggedin) {
- fprintf(stderr, "Not logged in!\n");
+ fprintf(stderr, "ERR: Not logged in!\n");
return;
}
@@ -315,7 +315,7 @@ list_cmd(const char *arg)
if (load_info(&info, f) == OK)
print_info(&info);
else
- fprintf(stderr, "Failed to read saved file info!\n");
+ fprintf(stderr, "ERR: Failed to read saved file info!\n");
fclose(f);
}
free(path);
@@ -331,7 +331,7 @@ auth_cmd(const char *arg)
int ret;
if (loggedin) {
- fprintf(stderr, "Already logged in!\n");
+ fprintf(stderr, "ERR: Already logged in!\n");
return;
}
@@ -343,7 +343,7 @@ auth_cmd(const char *arg)
} else if (ret && errno == EEXIST) {
printf("Success!\nWelcome back!\n");
} else {
- fprintf(stderr, "Auth failed!\n");
+ fprintf(stderr, "ERR: Auth failed!\n");
return;
}
@@ -369,7 +369,7 @@ main()
int exit, i, cmdlen;
if (!(envstr = getenv("RESULTDIR"))) {
- fprintf(stderr, "RESULTDIR not defined\n");
+ fprintf(stderr, "ERR: RESULTDIR not defined\n");
return 1;
}
diff --git a/src/stlfile.c b/src/stlfile.c
@@ -203,6 +203,9 @@ parse_file_ascii(struct parseinfo *info, char *buf, size_t len)
if (states.count)
PARSE_FAIL("Expected keyword, got:\n%.*s...\n", 30, bp);
+ bp = skipws(bp);
+ if (*bp) PARSE_FAIL("Extraneous data at end of file\n");
+
stack_free(&states);
return OK;
@@ -215,7 +218,7 @@ int
parse_file_bin(struct parseinfo *info, char *buf, size_t len)
{
char *bp, *end = buf + len;
- int i, k, m;
+ int i, k;
float v;
info->type = TYPE_BIN;
@@ -225,11 +228,13 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len)
memcpy(info->header, buf, 80);
- if (strlen(buf + 1))
+ if (*buf == '#' && strlen(buf + 1))
info->solidname = checkp(strdup(buf + 1));
bp = buf + 80;
+
info->loopcount = le32toh(*(uint32_t*)bp);
+ bp += 4;
if (!info->loopcount) {
memset(info->bbmax, 0, sizeof(float) * 3);
@@ -245,17 +250,20 @@ parse_file_bin(struct parseinfo *info, char *buf, size_t len)
for (i = 0; i < info->loopcount; i++) {
if (bp + 50 > end)
PARSE_FAIL("Truncated data! (loops missing)\n");
- bp += 12;
- for (k = 0; k < 3; k++, bp += 12) {
- for (m = 0; m < 3; m++) {
- v = fle32toh(*(float*)(bp + 4 * m));
- info->bbmin[m] = MIN(info->bbmin[m], v);
- info->bbmax[m] = MAX(info->bbmax[m], v);
+ for (k = 0; k < 12; k++, bp += 4) {
+ v = fle32toh(*(float*)bp);
+ if (v == INFINITY || v == NAN)
+ PARSE_FAIL("Encountered invalid float\n");
+ if (k >= 3) {
+ info->bbmin[k % 3] = MIN(info->bbmin[k % 3], v);
+ info->bbmax[k % 3] = MAX(info->bbmax[k % 3], v);
}
}
bp += 2;
}
+ if (bp != end) PARSE_FAIL("Extraneous data at end of file\n");
+
return OK;
fail:
@@ -271,8 +279,8 @@ parse_file(struct parseinfo *info, char *buf, size_t len)
if (info->valid) free_info(info);
- if (len < 7) {
- fprintf(stderr, "File too small!\n");
+ if (len < 10) {
+ fprintf(stderr, "ERR: File too small!\n");
return FAIL;
}
@@ -290,7 +298,7 @@ parse_file(struct parseinfo *info, char *buf, size_t len)
if (!info->modelname) {
resp = ask("Please enter your model name: ");
if (strlen(resp) < 4) {
- fprintf(stderr, "Model name is too short!\n");
+ fprintf(stderr, "ERR: Model name is too short!\n");
return FAIL;
}
info->modelname = checkp(strdup(resp));