Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions fsst.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ fsst_export(
void
fsst_destroy(fsst_encoder_t*);

/* Validate a serialized symbol table (as produced by fsst_export) before importing it.
Returns the number of header bytes fsst_import would consume (== its return value) if the
import is guaranteed memory-safe, or 0 if buf[0..bufLen) is malformed. Use this to screen
untrusted (possibly corrupted) input before calling fsst_import. */
size_t fsst_validate_header(unsigned char const *buf, size_t bufLen);

/* Validate a compressed string before decompressing it. Returns 1 if fsst_decompress cannot
over-read strIn[0..lenIn), 0 otherwise. */
int fsst_validate_compressed(unsigned char const *strIn, size_t lenIn);

/* Return a decoder structure from serialized format (typically used in a block-, file- or row-group header). */
unsigned int /* OUT: number of bytes consumed in buf (0 on failure). */
fsst_import(
Expand Down
52 changes: 52 additions & 0 deletions libfsst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -649,3 +649,55 @@ extern "C" fsst_decoder_t fsst_decoder(fsst_encoder_t *encoder) {
assert(cnt1 == cnt2); (void) cnt1; (void) cnt2;
return decoder;
}

// Validate a serialized FSST symbol table ("header") before handing it to fsst_import().
// This is a pure memory-safety predicate: it does not check whether the table decodes to meaningful
// data, only that fsst_import() cannot overrun the decoder arrays or read past the buffer.
extern "C" size_t fsst_validate_header(const unsigned char *buf, size_t bufLen) {
if (bufLen < 17) return 0; // version(8) + zeroTerminated(1) + lenHisto(8)

u64 version = 0; // same logic as fsst_import()
memcpy(&version, buf, 8);
version = swap64_if_be(version);
if ((version >> 32) != FSST_VERSION) return 0;

unsigned zeroTerminated = buf[8] & 1;
u8 lenHisto[8];
memcpy(lenHisto, buf + 9, 8);

unsigned code = zeroTerminated;
if (zeroTerminated) {
if (lenHisto[0] == 0) return 0; // fsst_import() would underflows lenHisto[0]
lenHisto[0]--;
}

size_t pos = 17;
for (unsigned l = 1; l <= 8; l++) {
const unsigned symLen = (l & 7) + 1; // 2,3,4,5,6,7,8,1 (same logic as fsst_import())
for (unsigned i = 0; i < lenHisto[l & 7]; i++) {
if (code >= 255) return 0; // fsst_import() would overflow the code table
pos += symLen;
code++;
}
}

if (pos > bufLen) return 0; // fsst_import() would read past the buffer

return pos;
}

// Validate a single FSST-compressed string before handing it to fsst_decompress().
// The only way fsst_decompress() can read past the input buffer is if the last input
// byte is a dangling escape.
extern "C" int fsst_validate_compressed(const unsigned char *strIn, size_t lenIn) {
size_t posIn = 0;
while (posIn < lenIn) {
if (strIn[posIn] == FSST_ESC) {
if (lenIn - posIn < 2) return 0; // last byte -> dangling escape
posIn += 2; // skip over the escaped byte
} else {
posIn += 1;
}
}
return 1;
}