tinymux/mux/modules/engine/sqlite_backend.cpp

163 lines
4 KiB
C++
Raw Permalink Normal View History

/*! \file sqlite_backend.cpp
* \brief SQLite implementation of IStorageBackend.
*
*/
#if !defined(TINYMUX_TYPES_DEFINED)
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#endif
#include "sqlite_backend.h"
#include <cstring>
CSQLiteBackend::CSQLiteBackend()
{
}
CSQLiteBackend::~CSQLiteBackend()
{
Close();
}
bool CSQLiteBackend::Open(const char *path)
{
return m_db.Open(path);
}
void CSQLiteBackend::Close()
{
m_db.Close();
}
bool CSQLiteBackend::IsOpen() const
{
return m_db.IsOpen();
}
bool CSQLiteBackend::Get(unsigned int object, unsigned int attrnum,
UTF8 *buf, size_t buflen, size_t *pLen,
int *owner, int *flags)
{
dbref db_owner;
int db_flags;
bool rc = m_db.GetAttribute(static_cast<dbref>(object), static_cast<int>(attrnum),
buf, buflen, pLen, &db_owner, &db_flags);
if (rc)
{
if (owner) *owner = static_cast<int>(db_owner);
if (flags) *flags = db_flags;
}
return rc;
}
bool CSQLiteBackend::Put(unsigned int object, unsigned int attrnum,
const UTF8 *value, size_t len,
int owner, int flags)
{
return m_db.PutAttribute(static_cast<dbref>(object), static_cast<int>(attrnum),
value, len, static_cast<dbref>(owner), flags);
}
bool CSQLiteBackend::Del(unsigned int object, unsigned int attrnum)
{
return m_db.DelAttribute(static_cast<dbref>(object), static_cast<int>(attrnum));
}
bool CSQLiteBackend::DelAll(unsigned int object)
{
return m_db.DelAllAttributes(static_cast<dbref>(object));
}
bool CSQLiteBackend::GetAll(unsigned int object, AttrCallback cb)
{
return m_db.GetAllAttributes(static_cast<dbref>(object),
[&cb](int attrnum, const UTF8 *value, size_t len, dbref owner, int flags)
{
cb(static_cast<unsigned int>(attrnum), value, len,
static_cast<int>(owner), flags);
});
}
bool CSQLiteBackend::GetBuiltin(unsigned int object, AttrCallback cb)
{
return m_db.GetBuiltinAttributes(static_cast<dbref>(object),
[&cb](int attrnum, const UTF8 *value, size_t len, dbref owner, int flags)
{
cb(static_cast<unsigned int>(attrnum), value, len,
static_cast<int>(owner), flags);
});
}
int CSQLiteBackend::Count(unsigned int object)
{
return m_db.CountAttributes(static_cast<dbref>(object));
}
uint32_t CSQLiteBackend::GetModCount(unsigned int object, unsigned int attrnum)
{
return m_db.GetAttrModCount(static_cast<dbref>(object),
static_cast<int>(attrnum));
}
bool CSQLiteBackend::GetAllModCounts(unsigned int object, ModCountCallback cb)
{
m_db.GetAllAttrModCounts(static_cast<dbref>(object),
[&cb](int attrnum, uint32_t mc)
{
cb(static_cast<unsigned int>(attrnum), mc);
});
return true;
}
void CSQLiteBackend::Sync()
{
m_db.Checkpoint();
}
void CSQLiteBackend::Tick()
{
// Write-through means SQLite is always up to date.
// Periodic optimize is light maintenance.
//
m_db.Optimize();
}
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
bool derive_sqlite_path(char *buf, size_t buflen, const UTF8 *indb)
{
if ( nullptr == buf
|| 0 == buflen
|| nullptr == indb)
{
return false;
}
static const char suffix[] = ".sqlite";
const size_t nSuffix = sizeof(suffix) - 1;
const char *pIn = reinterpret_cast<const char *>(indb);
size_t nIn = strlen(pIn);
// A trailing ".db" is replaced rather than appended to, so the base is
// shorter than the input in that case. Note this still grows the path:
// three characters out, seven in.
//
size_t nBase = nIn;
if ( 3 < nIn
&& 0 == strcmp(pIn + nIn - 3, ".db"))
{
nBase = nIn - 3;
}
if (buflen < nBase + nSuffix + 1)
{
return false;
}
memcpy(buf, pIn, nBase);
memcpy(buf + nBase, suffix, nSuffix + 1);
return true;
}