tinymux/mux/include/sqlite_backend.h
Stephen Dennis 776dab8d1a fix(engine): bound the .sqlite path derivation (#1411)
Two sites derived the ".sqlite" sibling of mudconf.indb by open-coding
the same replace-or-append with strcpy/strcat, neither checking the
remaining capacity:

  mux/modules/engine/engine_com.cpp  (bMinDB RemoveFile)  SIZEOF_PATHNAME
  mux/modules/engine/attrcache.cpp   (cache_init)         LBUF_SIZE

input_database is cf_string_dyn with a maximum of SIZEOF_PATHNAME, so its
content can be SIZEOF_PATHNAME-1 characters.  ".sqlite" is seven more plus
a terminator, so the engine_com buffer overflows in both branches: the
append writes eight bytes past a full buffer, and even the ".db" branch
that replaces three characters with seven needs four bytes it may not
have.  attrcache was safe only because indb is capped well below
LBUF_SIZE -- an implicit cross-module invariant, not a check.

Replaced with one bounds-checked derive_sqlite_path() in sqlite_backend,
which both TUs already include.  It returns false and leaves the buffer
untouched when the result will not fit; callers log and take a defined
path -- cache_init returns HF_OPEN_STATUS_ERROR after releasing the
backend it had just allocated, and the bMinDB case skips the removal,
since a path that cannot be formed names no file to remove.  No new
abort: the failure is reported, not asserted.

Behaviour is unchanged for every input that fits, including the edge
cases the old `n > 3` test produced -- ".db" appends rather than
replaces, "a.dbx" and "db" append -- so this is not a silent change of
the derived name.

Verified with ASan on a standalone harness carrying both versions, an
input_database at exactly the configured maximum:

  old, no .db suffix  heap-buffer-overflow, WRITE of size 8
  old, .db suffix     heap-buffer-overflow, WRITE of size 8
  new, both           returns false, no diagnostic

and the normal cases: netmux.db -> netmux.sqlite, netmux ->
netmux.sqlite, data/netmux.db -> data/netmux.sqlite, plus the exact-fit
boundary accepted and one byte over rejected.

smoke 1489/1489 on both routes, 0 crashes, 314/314 dispatched.

Closes #1411.
2026-07-26 13:59:30 -06:00

63 lines
2 KiB
C++

/*! \file sqlite_backend.h
* \brief SQLite implementation of IStorageBackend.
*
*/
#ifndef SQLITE_BACKEND_H
#define SQLITE_BACKEND_H
#include "storage_backend.h"
#include "sqlitedb.h"
class CSQLiteBackend : public IStorageBackend
{
public:
CSQLiteBackend();
~CSQLiteBackend() override;
// IStorageBackend interface.
//
bool Open(const char *path) override;
void Close() override;
bool IsOpen() const override;
bool Get(unsigned int object, unsigned int attrnum,
UTF8 *buf, size_t buflen, size_t *pLen,
int *owner, int *flags) override;
bool Put(unsigned int object, unsigned int attrnum,
const UTF8 *value, size_t len,
int owner, int flags) override;
bool Del(unsigned int object, unsigned int attrnum) override;
bool DelAll(unsigned int object) override;
bool GetAll(unsigned int object, AttrCallback cb) override;
bool GetBuiltin(unsigned int object, AttrCallback cb) override;
int Count(unsigned int object) override;
uint32_t GetModCount(unsigned int object, unsigned int attrnum) override;
bool GetAllModCounts(unsigned int object, ModCountCallback cb) override;
void Sync() override;
void Tick() override;
// Access the underlying CSQLiteDB for object metadata operations
// and statistics that are outside the IStorageBackend scope.
//
CSQLiteDB &GetDB() { return m_db; }
private:
CSQLiteDB m_db;
};
// Derive the ".sqlite" sibling of a database path: replace a trailing
// ".db" with ".sqlite", otherwise append ".sqlite".
//
// Returns false, leaving buf untouched, when the result would not fit.
// Both callers previously open-coded this with strcpy/strcat into a
// SIZEOF_PATHNAME buffer, which overflows for an input_database near the
// configured maximum -- the conf value may itself be SIZEOF_PATHNAME-1
// long, and ".sqlite" needs seven more bytes plus a terminator (#1411).
//
bool derive_sqlite_path(char *buf, size_t buflen, const UTF8 *indb);
#endif // !SQLITE_BACKEND_H