osquery-1/osquery/sql/virtual_table.cpp

323 lines
9.4 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
2014-12-03 04:36:46 +00:00
#include <osquery/logger.h>
2015-01-12 18:02:44 +00:00
2015-02-03 05:21:36 +00:00
#include "osquery/sql/virtual_table.h"
2014-12-03 04:36:46 +00:00
namespace osquery {
namespace tables {
2014-12-03 05:02:50 +00:00
int xOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) {
int rc = SQLITE_NOMEM;
2015-01-30 18:44:25 +00:00
BaseCursor *pCur;
2014-12-03 05:02:50 +00:00
2015-01-30 18:44:25 +00:00
pCur = new BaseCursor;
2014-12-03 05:02:50 +00:00
if (pCur) {
2015-01-30 18:44:25 +00:00
memset(pCur, 0, sizeof(BaseCursor));
2014-12-03 05:02:50 +00:00
*ppCursor = (sqlite3_vtab_cursor *)pCur;
rc = SQLITE_OK;
}
return rc;
}
int xClose(sqlite3_vtab_cursor *cur) {
2015-01-30 18:44:25 +00:00
BaseCursor *pCur = (BaseCursor *)cur;
2014-12-03 05:02:50 +00:00
delete pCur;
return SQLITE_OK;
}
2014-12-03 16:29:36 +00:00
int xEof(sqlite3_vtab_cursor *cur) {
2015-01-30 18:44:25 +00:00
BaseCursor *pCur = (BaseCursor *)cur;
auto *pVtab = (VirtualTable *)cur->pVtab;
return pCur->row >= pVtab->content->n;
2014-12-03 16:29:36 +00:00
}
int xDestroy(sqlite3_vtab *p) {
2015-01-30 18:44:25 +00:00
auto *pVtab = (VirtualTable *)p;
delete pVtab->content;
2014-12-03 16:29:36 +00:00
delete pVtab;
return SQLITE_OK;
}
2014-12-03 05:02:50 +00:00
int xNext(sqlite3_vtab_cursor *cur) {
2015-01-30 18:44:25 +00:00
BaseCursor *pCur = (BaseCursor *)cur;
2014-12-03 05:02:50 +00:00
pCur->row++;
return SQLITE_OK;
}
int xRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid) {
2015-01-30 18:44:25 +00:00
BaseCursor *pCur = (BaseCursor *)cur;
2014-12-03 05:02:50 +00:00
*pRowid = pCur->row;
return SQLITE_OK;
}
2015-01-30 18:44:25 +00:00
int xCreate(sqlite3 *db,
void *pAux,
int argc,
const char *const *argv,
sqlite3_vtab **ppVtab,
char **pzErr) {
auto *pVtab = new VirtualTable;
if (!pVtab || argc == 0 || argv[0] == nullptr) {
return SQLITE_NOMEM;
}
memset(pVtab, 0, sizeof(VirtualTable));
pVtab->content = new VirtualTableContent;
PluginResponse response;
pVtab->content->name = std::string(argv[0]);
// Get the table column information.
auto status = Registry::call(
"table", pVtab->content->name, {{"action", "columns"}}, response);
if (!status.ok() || response.size() == 0) {
return SQLITE_ERROR;
}
auto statement =
"CREATE TABLE " + pVtab->content->name + columnDefinition(response);
int rc = sqlite3_declare_vtab(db, statement.c_str());
2015-01-30 18:44:25 +00:00
if (rc != SQLITE_OK) {
return rc;
}
if (!status.ok() || response.size() == 0) {
return SQLITE_ERROR;
}
2015-01-30 18:44:25 +00:00
for (const auto &column : response) {
pVtab->content->columns.push_back(
std::make_pair(column.at("name"), column.at("type")));
}
*ppVtab = (sqlite3_vtab *)pVtab;
return rc;
}
int xColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) {
BaseCursor *pCur = (BaseCursor *)cur;
auto *pVtab = (VirtualTable *)cur->pVtab;
if (col >= pVtab->content->columns.size()) {
return SQLITE_ERROR;
}
2015-07-09 05:37:35 +00:00
auto &column_name = pVtab->content->columns[col].first;
auto &type = pVtab->content->columns[col].second;
2015-01-30 18:44:25 +00:00
if (pCur->row >= pVtab->content->data[column_name].size()) {
return SQLITE_ERROR;
}
2015-07-09 05:37:35 +00:00
// Attempt to cast each xFilter-populated row/column to the SQLite type.
auto &value = pVtab->content->data[column_name][pCur->row];
2015-01-30 18:44:25 +00:00
if (type == "TEXT") {
2015-07-15 02:09:55 +00:00
sqlite3_result_text(ctx, value.c_str(), value.size(), SQLITE_STATIC);
2015-01-30 18:44:25 +00:00
} else if (type == "INTEGER") {
char *end;
2015-01-30 18:44:25 +00:00
int afinite;
long int val = strtol(value.c_str(), &end, 10);
if (end == value.c_str() || *end != '\0' ||
((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE) ||
val < INT_MIN || val > INT_MAX) {
2015-01-30 18:44:25 +00:00
afinite = -1;
2015-02-11 22:27:19 +00:00
VLOG(1) << "Error casting " << column_name << " (" << value
<< ") to INTEGER";
} else {
afinite = (int)val;
2015-01-30 18:44:25 +00:00
}
sqlite3_result_int(ctx, afinite);
} else if (type == "BIGINT") {
char *end;
long long int afinite = strtoll(value.c_str(), &end, 10);
if (end == value.c_str() || *end != '\0' ||
((afinite == LLONG_MIN || afinite == LLONG_MAX) && errno == ERANGE)) {
2015-01-30 18:44:25 +00:00
afinite = -1;
2015-02-11 22:27:19 +00:00
VLOG(1) << "Error casting " << column_name << " (" << value
<< ") to BIGINT";
2015-01-30 18:44:25 +00:00
}
sqlite3_result_int64(ctx, afinite);
} else if (type == "DOUBLE") {
double afinite;
try {
afinite = boost::lexical_cast<double>(value);
} catch (const boost::bad_lexical_cast &e) {
afinite = 0;
VLOG(1) << "Error casting" << column_name << " (" << value
<< ") to DOUBLE";
}
sqlite3_result_double(ctx, afinite);
2015-01-30 18:44:25 +00:00
}
return SQLITE_OK;
}
static int xBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo) {
auto *pVtab = (VirtualTable *)tab;
pVtab->content->constraints.clear();
int expr_index = 0;
int cost = 0;
for (size_t i = 0; i < pIdxInfo->nConstraint; ++i) {
if (!pIdxInfo->aConstraint[i].usable) {
// A higher cost less priority, prefer more usable query constraints.
cost += 10;
// TODO: OR is not usable.
continue;
2014-12-03 04:36:46 +00:00
}
2015-01-30 18:44:25 +00:00
const auto &name =
pVtab->content->columns[pIdxInfo->aConstraint[i].iColumn].first;
pVtab->content->constraints.push_back(
std::make_pair(name, Constraint(pIdxInfo->aConstraint[i].op)));
pIdxInfo->aConstraintUsage[i].argvIndex = ++expr_index;
}
pIdxInfo->estimatedCost = cost;
return SQLITE_OK;
}
static int xFilter(sqlite3_vtab_cursor *pVtabCursor,
int idxNum,
const char *idxStr,
int argc,
sqlite3_value **argv) {
BaseCursor *pCur = (BaseCursor *)pVtabCursor;
auto *pVtab = (VirtualTable *)pVtabCursor->pVtab;
pCur->row = 0;
pVtab->content->n = 0;
QueryContext context;
for (size_t i = 0; i < pVtab->content->columns.size(); ++i) {
2015-07-09 05:37:35 +00:00
// Clear any data, this is the result container for each column + row.
2015-01-30 18:44:25 +00:00
pVtab->content->data[pVtab->content->columns[i].first].clear();
2015-07-09 05:37:35 +00:00
// Set the column affinity for each optional constraint list.
// There is a separate list for each column name.
2015-01-30 18:44:25 +00:00
context.constraints[pVtab->content->columns[i].first].affinity =
pVtab->content->columns[i].second;
2014-12-03 04:36:46 +00:00
}
2015-01-30 18:44:25 +00:00
2015-07-09 05:37:35 +00:00
// Iterate over every argument to xFilter, filling in constraint values.
2015-01-30 18:44:25 +00:00
for (size_t i = 0; i < argc; ++i) {
auto expr = (const char *)sqlite3_value_text(argv[i]);
if (expr == nullptr) {
// SQLite did not expose the expression value.
continue;
}
2015-01-30 18:44:25 +00:00
// Set the expression from SQLite's now-populated argv.
pVtab->content->constraints[i].second.expr = std::string(expr);
// Add the constraint to the column-sorted query request map.
context.constraints[pVtab->content->constraints[i].first].add(
pVtab->content->constraints[i].second);
}
PluginRequest request = {{"action", "generate"}};
2015-01-30 18:44:25 +00:00
PluginResponse response;
TablePlugin::setRequestFromContext(context, request);
Registry::call("table", pVtab->content->name, request, response);
2015-01-30 18:44:25 +00:00
// Now organize the response rows by column instead of row.
2015-03-18 19:01:58 +00:00
auto &data = pVtab->content->data;
2015-07-09 05:37:35 +00:00
for (auto &row : response) {
2015-01-30 18:44:25 +00:00
for (const auto &column : pVtab->content->columns) {
2015-07-09 05:37:35 +00:00
if (row.count(column.first) == 0) {
VLOG(1) << "Table " << pVtab->content->name << " row "
<< pVtab->content->n << " did not include column "
<< column.first;
2015-03-18 19:01:58 +00:00
data[column.first].push_back("");
2015-07-09 05:37:35 +00:00
continue;
}
auto &value = row.at(column.first);
if (value.size() > FLAGS_value_max) {
data[column.first].push_back(value.substr(0, FLAGS_value_max));
value.clear();
} else {
data[column.first].push_back(std::move(value));
}
2015-01-30 18:44:25 +00:00
}
2015-03-18 19:01:58 +00:00
2015-01-30 18:44:25 +00:00
pVtab->content->n++;
}
return SQLITE_OK;
}
}
2015-01-30 18:44:25 +00:00
Status attachTableInternal(const std::string &name,
const std::string &statement,
sqlite3 *db) {
if (SQLiteDBManager::isDisabled(name)) {
VLOG(0) << "Table " << name << " is disabled, not attaching";
return Status(0, getStringForSQLiteReturnCode(0));
}
// A static module structure does not need specific logic per-table.
// clang-format off
2015-01-30 18:44:25 +00:00
static sqlite3_module module = {
0,
tables::xCreate,
tables::xCreate,
tables::xBestIndex,
tables::xDestroy,
tables::xDestroy,
tables::xOpen,
tables::xClose,
tables::xFilter,
tables::xNext,
tables::xEof,
tables::xColumn,
tables::xRowid,
2015-01-30 18:44:25 +00:00
};
// clang-format on
2015-01-30 18:44:25 +00:00
// Note, if the clientData API is used then this will save a registry call
// within xCreate.
int rc = sqlite3_create_module(db, name.c_str(), &module, 0);
if (rc == SQLITE_OK || rc == SQLITE_MISUSE) {
auto format =
"CREATE VIRTUAL TABLE temp." + name + " USING " + name + statement;
2015-04-27 09:12:58 +00:00
rc = sqlite3_exec(db, format.c_str(), nullptr, nullptr, 0);
} else {
LOG(ERROR) << "Error attaching table: " << name << " (" << rc << ")";
}
return Status(rc, getStringForSQLiteReturnCode(rc));
}
Status detachTableInternal(const std::string &name, sqlite3 *db) {
auto format = "DROP TABLE IF EXISTS temp." + name;
2015-04-27 09:12:58 +00:00
int rc = sqlite3_exec(db, format.c_str(), nullptr, nullptr, 0);
if (rc != SQLITE_OK) {
LOG(ERROR) << "Error detaching table: " << name << " (" << rc << ")";
2015-01-30 18:44:25 +00:00
}
return Status(rc, getStringForSQLiteReturnCode(rc));
2014-12-03 04:36:46 +00:00
}
void attachVirtualTables(sqlite3 *db) {
PluginResponse response;
for (const auto &name : Registry::names("table")) {
// Column information is nice for virtual table create call.
auto status =
Registry::call("table", name, {{"action", "columns"}}, response);
if (status.ok()) {
auto statement = columnDefinition(response);
attachTableInternal(name, statement, db);
2014-12-03 04:36:46 +00:00
}
}
}
}