diff --git a/c/json.h b/c/json.h index 3e1c13d..8a35e24 100644 --- a/c/json.h +++ b/c/json.h @@ -26,8 +26,14 @@ typedef struct { const char *s; char *arena; /* buffer per le stringhe smontate */ size_t acap, aoff; + int depth; /* annidamento corrente: bound contro lo stack-overflow + * da JSON malevolo tipo [[[[...]]]] (discesa ricorsiva) */ } jparser; +/* tetto di annidamento: gli header safetensors / config sono piatti (profondita' + * ~3). 1024 e' larghissimo per input legittimi e ben sotto il limite di stack. */ +#define J_MAX_DEPTH 1024 + static char *j_dup(jparser *p, const char *b, int n) { /* ogni stringa ha la sua allocazione: un'arena con realloc sposterebbe il * buffer invalidando i puntatori gia' emessi (use-after-free). */ @@ -91,10 +97,11 @@ static jval *j_parse_val(jparser *p) { char c = *p->s; if (c == '"') { jval *v = j_new(J_STR); v->str = j_parse_str_raw(p); return v; } if (c == '{') { + if (++p->depth > J_MAX_DEPTH) { p->depth--; return j_new(J_NULL); } p->s++; jval *v = j_new(J_OBJ); int cap = 8; v->keys = malloc(cap * sizeof(char*)); v->kids = malloc(cap * sizeof(jval*)); j_ws(p); - if (*p->s == '}') { p->s++; return v; } + if (*p->s == '}') { p->s++; p->depth--; return v; } for (;;) { j_ws(p); char *key = j_parse_str_raw(p); @@ -107,13 +114,15 @@ static jval *j_parse_val(jparser *p) { if (*p->s == '}') { p->s++; break; } break; } + p->depth--; return v; } if (c == '[') { + if (++p->depth > J_MAX_DEPTH) { p->depth--; return j_new(J_NULL); } p->s++; jval *v = j_new(J_ARR); int cap = 8; v->kids = malloc(cap * sizeof(jval*)); j_ws(p); - if (*p->s == ']') { p->s++; return v; } + if (*p->s == ']') { p->s++; p->depth--; return v; } for (;;) { jval *val = j_parse_val(p); if (v->len == cap) { cap *= 2; v->kids = realloc(v->kids, cap*sizeof(jval*)); } @@ -123,6 +132,7 @@ static jval *j_parse_val(jparser *p) { if (*p->s == ']') { p->s++; break; } break; } + p->depth--; return v; } if (c == 't') { p->s += 4; jval *v = j_new(J_BOOL); v->boolean = 1; return v; } @@ -134,7 +144,7 @@ static jval *j_parse_val(jparser *p) { /* API */ static jval *json_parse(const char *text, char **arena_out) { - jparser p = { text, NULL, 0, 0 }; + jparser p = { text, NULL, 0, 0, 0 }; jval *v = j_parse_val(&p); if (arena_out) *arena_out = p.arena; else free(p.arena); return v; diff --git a/c/st.h b/c/st.h index 08d0af6..6b4a710 100644 --- a/c/st.h +++ b/c/st.h @@ -14,9 +14,15 @@ #include #include #include +#include #include "json.h" #include "compat.h" +/* tetto sulla dimensione dell'header safetensors: gli header reali sono piccoli + * (KB..pochi MB). Un file crafted che dichiara un hlen enorme causerebbe una + * malloc gigante prima ancora di leggere: lo respingiamo. */ +#define ST_MAX_HEADER (512ll << 20) + typedef struct { char *name; int fd; @@ -118,14 +124,27 @@ static void st_init(shards *S, const char *snap_dir) { for (int fi = 0; fi < nf; fi++) { int fd = st_open_fd(S, files[fi]); + struct stat sst; + if (fstat(fd, &sst) != 0) { perror("fstat shard"); exit(1); } + int64_t fsz = (int64_t)sst.st_size; uint64_t hlen; if (pread(fd, &hlen, 8, 0) != 8) { perror("pread hlen"); exit(1); } + /* file malevolo/troncato: hlen deve stare nel file dopo gli 8 byte di + * prefisso e sotto il tetto. Senza questo bound hlen+1 puo' andare in + * overflow (malloc(0) e poi hdr[hlen]=0 fuori limiti) o forzare una + * malloc gigante. */ + if (fsz < 8 || hlen > (uint64_t)(fsz - 8) || hlen > (uint64_t)ST_MAX_HEADER) { + fprintf(stderr, "%s: bad safetensors header length %llu (file %lld bytes)\n", + files[fi], (unsigned long long)hlen, (long long)fsz); exit(1); } char *hdr = malloc(hlen + 1); + if (!hdr) { perror("malloc safetensors header"); exit(1); } if (pread(fd, hdr, hlen, 8) != (ssize_t)hlen) { perror("pread hdr"); exit(1); } hdr[hlen] = 0; int64_t data_start = 8 + (int64_t)hlen; char *arena = NULL; jval *root = json_parse(hdr, &arena); + if (!root || root->t != J_OBJ) { + fprintf(stderr, "%s: safetensors header is not a JSON object\n", files[fi]); exit(1); } for (int i = 0; i < root->len; i++) { const char *name = root->keys[i]; if (!strcmp(name, "__metadata__")) continue; @@ -133,7 +152,21 @@ static void st_init(shards *S, const char *snap_dir) { jval *dt = json_get(m, "dtype"); jval *off = json_get(m, "data_offsets"); jval *shp = json_get(m, "shape"); + /* un header crafted puo' omettere i campi o dare tipi sbagliati: + * senza questi guard si dereferenzia NULL (json_get) o si legge + * off->kids[0/1] oltre i limiti dell'array. */ + if (!dt || dt->t != J_STR || !off || off->t != J_ARR || off->len < 2 || + !shp || shp->t != J_ARR) { + fprintf(stderr, "%s: tensor '%s' has malformed dtype/data_offsets/shape\n", + files[fi], name); exit(1); } int64_t a0 = (int64_t)off->kids[0]->num, b0 = (int64_t)off->kids[1]->num; + /* offset dichiarati dal file: non-negativi, ordinati e dentro al + * file. Altrimenti nbytes=b0-a0 diventa negativo -> malloc((size_t)) + * gigante e la memcpy in st_read_f32 sfora il buffer del chiamante; + * oppure off punta fuori dal file. */ + if (a0 < 0 || b0 < a0 || data_start + b0 > fsz) { + fprintf(stderr, "%s: tensor '%s' data_offsets [%lld,%lld] out of file bounds (%lld)\n", + files[fi], name, (long long)a0, (long long)b0, (long long)fsz); exit(1); } int64_t numel = 1; for (int k = 0; k < shp->len; k++) numel *= (int64_t)shp->kids[k]->num; if (S->n == S->cap) { S->cap *= 2; S->t = realloc(S->t, S->cap*sizeof(st_tensor)); } st_tensor *t = &S->t[S->n++]; @@ -184,6 +217,7 @@ static int64_t st_read_f32(shards *S, const char *name, float *out, int drop) { st_tensor *t = st_find(S, name); if (!t) { fprintf(stderr, "missing tensor: %s\n", name); exit(1); } void *raw = malloc(t->nbytes); + if (!raw) { fprintf(stderr, "malloc %lld bytes for tensor %s failed\n", (long long)t->nbytes, name); exit(1); } if (pread(t->fd, raw, t->nbytes, t->off) != t->nbytes) { perror("pread data"); exit(1); } if (t->dtype == 2) { memcpy(out, raw, t->nbytes); diff --git a/c/tools/eval_glm.py b/c/tools/eval_glm.py index d750921..3aa568e 100644 --- a/c/tools/eval_glm.py +++ b/c/tools/eval_glm.py @@ -134,8 +134,11 @@ def main(): for r in reqs[:3]: print(" example request:", r[:80], "...", file=sys.stderr) print("DRY: request construction and tokenization passed. Engine was not run.", file=sys.stderr); return - req_path = tempfile.mktemp(suffix=".txt") - open(req_path, "w").write("\n".join(reqs) + "\n") + # mkstemp (non mktemp): crea il file atomicamente con permessi 0600, niente + # race TOCTOU/symlink su una tmp dir condivisa (CWE-377). + fd, req_path = tempfile.mkstemp(suffix=".txt") + with os.fdopen(fd, "w") as f: + f.write("\n".join(reqs) + "\n") env = dict(os.environ, SNAP=a.snap, SCORE=req_path) if a.ram: env["RAM_GB"] = str(a.ram) cmd = [a.glm, str(a.cap)] + a.bits.split()