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
39 changes: 33 additions & 6 deletions src/store/splay.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,34 @@ static bool table_has_col_named(ray_t* tbl, const char* name, size_t len) {
return false;
}

static bool splay_col_name_safe(const char* name, size_t name_len) {
return name_len > 0 && name[0] != '.' &&
!memchr(name, '/', name_len) &&
!memchr(name, '\\', name_len) &&
!memchr(name, '\0', name_len);
}

static ray_err_t splay_validate_persisted_names(ray_t* tbl) {
int64_t nc = ray_table_ncols(tbl);
for (int64_t c = 0; c < nc; c++) {
ray_t* a = ray_sym_str(ray_table_col_name(tbl, c));
if (!a || RAY_IS_ERR(a)) continue;
const char* an = ray_str_ptr(a);
size_t alen = ray_str_len(a);
if (!splay_col_name_safe(an, alen)) continue;
for (int64_t j = c + 1; j < nc; j++) {
ray_t* b = ray_sym_str(ray_table_col_name(tbl, j));
if (!b || RAY_IS_ERR(b)) continue;
const char* bn = ray_str_ptr(b);
size_t blen = ray_str_len(b);
if (!splay_col_name_safe(bn, blen)) continue;
if (alen == blen && memcmp(an, bn, alen) == 0)
return RAY_ERR_DOMAIN;
}
}
return RAY_OK;
}

/* Remove regular files in `dir` that are not part of the just-written
* table: not a dotfile (".d", ".sym", ".sym.lk"), not a current column.
* Runs after the .d commit so a stale wider-schema file can never shadow
Expand Down Expand Up @@ -119,6 +147,9 @@ static ray_err_t splay_save_impl(ray_t* tbl, const char* dir, const char* sym_pa
if (!tbl || RAY_IS_ERR(tbl)) return RAY_ERR_TYPE;
if (!dir) return RAY_ERR_IO;

ray_err_t name_err = splay_validate_persisted_names(tbl);
if (name_err != RAY_OK) return name_err;

/* Symfile/column collision guard. A column is written as `dir/<name>`;
* the symfile (and its `<sym>.lk` lock) is written at `sym_path`. A
* column whose file lands on the symfile path — or its lock path —
Expand Down Expand Up @@ -237,9 +268,7 @@ static ray_err_t splay_save_impl(ray_t* tbl, const char* dir, const char* sym_pa
if (!name_atom) continue;
const char* name = ray_str_ptr(name_atom);
size_t name_len = ray_str_len(name_atom);
if (name_len == 0 || name[0] == '.' ||
memchr(name, '/', name_len) || memchr(name, '\\', name_len) ||
memchr(name, '\0', name_len))
if (!splay_col_name_safe(name, name_len))
continue; /* unsafe name: no file, no .d entry */

char path[1024];
Expand Down Expand Up @@ -359,9 +388,7 @@ static ray_t* splay_load_dom_impl(const char* dir, ray_sym_domain_t* dom,

/* Reject names with path separators, traversal, or starting with '.'
* — these indicate a corrupt/hand-tampered .d. */
if (name_len == 0 || name[0] == '.' ||
memchr(name, '/', name_len) || memchr(name, '\\', name_len) ||
memchr(name, '\0', name_len)) {
if (!splay_col_name_safe(name, name_len)) {
ray_release(schema);
ray_release(tbl);
return ray_error("corrupt",
Expand Down
10 changes: 10 additions & 0 deletions test/rfl/system/splayed.rfl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@
(at R-Small 'a) -- (at T-Small 'a)
(at R-Small 'b) -- (at T-Small 'b)

;; ────────────── duplicate persisted names are rejected ──────────────
;; Splayed stores one file per column name. A table with duplicate column
;; names used to write both columns to the same path, so the later column
;; overwrote the earlier one and reload duplicated the later data.
(.sys.exec "rm -rf /tmp/rfl_splayed_dup")
(set J-L (table [sym qty px] (list ['A 'B] [10 20] [1.0 2.0])))
(set J-R (table [sym qty venue] (list ['A 'C] [100 300] ['X 'Z])))
(.db.splayed.set "/tmp/rfl_splayed_dup/join/" (full-join [sym] J-L J-R)) !- domain
(.db.splayed.set "/tmp/rfl_splayed_dup/csv/" (table [a a] (list [1 2] [10 20]))) !- domain

;; ────────────── morsel-boundary table (1024 rows) ──────────────
(set T-1024 (table [n] (list (til 1024))))
(.db.splayed.set "/tmp/rfl_splayed_1024/" T-1024)
Expand Down
Loading