osquery-1/osquery/sql/virtual_table.cpp

482 lines
16 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2014-present, 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 <atomic>
#include <osquery/flags.h>
#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 {
FLAG(bool, enable_foreign, false, "Enable noop foreign virtual tables");
SHELL_FLAG(bool, planner, false, "Enable osquery runtime planner output");
2016-03-20 23:05:13 +00:00
/**
* @brief A protection around concurrent table attach requests.
*
* Table attaching is not concurrent. Attaching is the only unprotected SQLite
* operation from osquery's usage perspective. The extensions API allows for
* concurrent access of non-thread-safe database resources for attaching table
* schema and filter routing instructions.
*/
Mutex kAttachMutex;
/**
* A generated foreign amalgamation file includes schema for all tables.
*
* When the build system generates TablePlugin%s from the .table spec files, it
* reads the foreign-platform tables and generates an associated schema plugin.
* These plugins are amalgamated into 'foreign_amalgamation' and do not call
* their filter generation functions.
*/
void registerForeignTables();
2014-12-03 04:36:46 +00:00
namespace tables {
namespace sqlite {
2014-12-03 04:36:46 +00:00
/// For planner and debugging an incrementing cursor ID is used.
static std::atomic<size_t> kPlannerCursorID{0};
/**
* @brief A next-ID for within-query constraints stacking.
*
* As constraints are evaluated within xBestIndex, an IDX is assigned for
* operator and operand retrieval during xFilter/scanning.
*/
static std::atomic<size_t> kConstraintIndexID{0};
static inline std::string opString(unsigned char op) {
switch (op) {
case EQUALS:
return "=";
case GREATER_THAN:
return ">";
case LESS_THAN_OR_EQUALS:
return "<=";
case LESS_THAN:
return "<";
case GREATER_THAN_OR_EQUALS:
return ">=";
2016-02-17 07:10:08 +00:00
case LIKE:
return "LIKE";
case MATCH:
return "MATCH";
case GLOB:
return "GLOB";
case REGEXP:
return "REGEX";
case UNIQUE:
return "UNIQUE";
}
return "?";
}
static void plan(const std::string &output) {
if (FLAGS_planner) {
fprintf(stderr, "osquery planner: %s\n", output.c_str());
}
}
int xOpen(sqlite3_vtab *tab, sqlite3_vtab_cursor **ppCursor) {
2014-12-03 05:02:50 +00:00
int rc = SQLITE_NOMEM;
auto *pCur = new BaseCursor;
auto *pVtab = (VirtualTable *)tab;
if (pCur != nullptr) {
plan("Opening cursor (" + std::to_string(kPlannerCursorID) +
") for table: " + pVtab->content->name);
pCur->id = kPlannerCursorID++;
pCur->base.pVtab = tab;
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;
plan("Closing cursor (" + std::to_string(pCur->id) + ")");
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;
2015-11-20 19:03:57 +00:00
if (pCur->row >= pCur->n) {
2015-08-17 23:58:54 +00:00
// If the requested row exceeds the size of the row set then all rows
// have been visited, clear the data container.
return true;
}
return false;
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-08-17 23:58:54 +00:00
const 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) {
delete pVtab;
return SQLITE_NOMEM;
2015-01-30 18:44:25 +00:00
}
memset(pVtab, 0, sizeof(VirtualTable));
pVtab->content = new VirtualTableContent;
pVtab->instance = (SQLiteDBInstance *)pAux;
2015-01-30 18:44:25 +00:00
2015-11-20 19:03:57 +00:00
// Create a TablePlugin Registry call, expect column details as the response.
2015-01-30 18:44:25 +00:00
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) {
delete pVtab->content;
delete pVtab;
return SQLITE_ERROR;
}
2015-11-20 19:03:57 +00:00
// Generate an SQL create table statement from the retrieved column details.
auto statement =
"CREATE TABLE " + pVtab->content->name + columnDefinition(response);
int rc = sqlite3_declare_vtab(db, statement.c_str());
if (rc != SQLITE_OK || !status.ok() || response.size() == 0) {
delete pVtab->content;
delete pVtab;
return (rc != SQLITE_OK) ? rc : SQLITE_ERROR;
}
2015-11-20 19:03:57 +00:00
// Keep a local copy of the column details in the VirtualTableContent struct.
// This allows introspection into the column type without additional calls.
2015-01-30 18:44:25 +00:00
for (const auto &column : response) {
pVtab->content->columns.push_back(std::make_tuple(
column.at("name"), columnTypeName(column.at("type")),
(ColumnOptions)AS_LITERAL(INTEGER_LITERAL, column.at("op"))));
2015-01-30 18:44:25 +00:00
}
*ppVtab = (sqlite3_vtab *)pVtab;
return rc;
}
int xColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) {
2015-11-20 19:03:57 +00:00
BaseCursor *pCur = (BaseCursor *)cur;
2015-08-17 23:58:54 +00:00
const auto *pVtab = (VirtualTable *)cur->pVtab;
if (col >= static_cast<int>(pVtab->content->columns.size())) {
2015-08-17 23:58:54 +00:00
// Requested column index greater than column set size.
2015-01-30 18:44:25 +00:00
return SQLITE_ERROR;
}
const auto &column_name = std::get<0>(pVtab->content->columns[col]);
const auto &type = std::get<1>(pVtab->content->columns[col]);
2015-11-20 19:03:57 +00:00
if (pCur->row >= pCur->data.size()) {
2015-08-17 23:58:54 +00:00
// Request row index greater than row set size.
2015-01-30 18:44:25 +00:00
return SQLITE_ERROR;
}
2015-07-09 05:37:35 +00:00
// Attempt to cast each xFilter-populated row/column to the SQLite type.
2015-11-20 19:03:57 +00:00
const auto &value = pCur->data[pCur->row][column_name];
if (pCur->data[pCur->row].count(column_name) == 0) {
// Missing content.
VLOG(1) << "Error " << column_name << " is empty";
sqlite3_result_null(ctx);
} else if (type == TEXT_TYPE) {
2015-07-15 02:09:55 +00:00
sqlite3_result_text(ctx, value.c_str(), value.size(), SQLITE_STATIC);
} else if (type == INTEGER_TYPE) {
2015-11-01 09:12:48 +00:00
long afinite;
if (!safeStrtol(value, 10, afinite) || afinite < INT_MIN ||
afinite > INT_MAX) {
2015-02-11 22:27:19 +00:00
VLOG(1) << "Error casting " << column_name << " (" << value
<< ") to INTEGER";
sqlite3_result_null(ctx);
} else {
sqlite3_result_int(ctx, (int)afinite);
2015-01-30 18:44:25 +00:00
}
} else if (type == BIGINT_TYPE || type == UNSIGNED_BIGINT_TYPE) {
2015-11-01 09:12:48 +00:00
long long afinite;
if (!safeStrtoll(value, 10, afinite)) {
2015-02-11 22:27:19 +00:00
VLOG(1) << "Error casting " << column_name << " (" << value
<< ") to BIGINT";
sqlite3_result_null(ctx);
} else {
sqlite3_result_int64(ctx, afinite);
2015-01-30 18:44:25 +00:00
}
} else if (type == DOUBLE_TYPE) {
char *end = nullptr;
double afinite = strtod(value.c_str(), &end);
if (end == nullptr || end == value.c_str() || *end != '\0') {
afinite = 0;
VLOG(1) << "Error casting " << column_name << " (" << value
<< ") to DOUBLE";
sqlite3_result_null(ctx);
} else {
sqlite3_result_double(ctx, afinite);
}
} else {
LOG(ERROR) << "Error unknown column type " << column_name;
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;
2015-11-20 19:03:57 +00:00
ConstraintSet constraints;
// Keep track of the index used for each valid constraint.
// Expect this index to correspond with argv within xFilter.
size_t expr_index = 0;
// If any constraints are unusable increment the cost of the index.
2016-03-02 17:49:49 +00:00
double cost = 1;
2015-11-20 19:03:57 +00:00
// Expressions operating on the same virtual table are loosely identified by
// the consecutive sets of terms each of the constraint sets are applied onto.
// Subsequent attempts from failed (unusable) constraints replace the set,
// while new sets of terms append.
if (pIdxInfo->nConstraint > 0) {
for (size_t i = 0; i < static_cast<size_t>(pIdxInfo->nConstraint); ++i) {
2015-11-20 19:03:57 +00:00
// Record the term index (this index exists across all expressions).
2016-01-27 19:27:00 +00:00
const auto &constraint_info = pIdxInfo->aConstraint[i];
#if defined(DEBUG)
plan("Evaluating constraints for table: " + pVtab->content->name +
" [index=" + std::to_string(i) + " column=" +
2016-01-27 19:27:00 +00:00
std::to_string(constraint_info.iColumn) + " term=" +
std::to_string((int)constraint_info.iTermOffset) + " usable=" +
2016-01-27 19:27:00 +00:00
std::to_string((int)constraint_info.usable) + "]");
#endif
2016-01-27 19:27:00 +00:00
if (!constraint_info.usable) {
// A higher cost less priority, prefer more usable query constraints.
cost += 10;
continue;
}
2015-11-20 19:03:57 +00:00
// Lookup the column name given an index into the table column set.
2016-01-27 19:27:00 +00:00
if (constraint_info.iColumn < 0 ||
static_cast<size_t>(constraint_info.iColumn) >=
pVtab->content->columns.size()) {
cost += 10;
continue;
}
const auto &name =
std::get<0>(pVtab->content->columns[constraint_info.iColumn]);
2015-11-20 19:03:57 +00:00
// Save a pair of the name and the constraint operator.
// Use this constraint during xFilter by performing a scan and column
// name lookup through out all cursor constraint lists.
constraints.push_back(
2016-01-27 19:27:00 +00:00
std::make_pair(name, Constraint(constraint_info.op)));
pIdxInfo->aConstraintUsage[i].argvIndex = ++expr_index;
#if defined(DEBUG)
plan("Adding constraint for table: " + pVtab->content->name +
" [column=" + name + " arg_index=" + std::to_string(expr_index) +
"]");
#endif
2014-12-03 04:36:46 +00:00
}
2015-01-30 18:44:25 +00:00
}
pIdxInfo->idxNum = kConstraintIndexID++;
#if defined(DEBUG)
plan("Recording constraint set for table: " + pVtab->content->name +
" [cost=" + std::to_string(cost) + " size=" +
std::to_string(constraints.size()) + " idx=" +
std::to_string(pIdxInfo->idxNum) + "]");
#endif
// Add the constraint set to the table's tracked constraints.
pVtab->content->constraints[pIdxInfo->idxNum] = std::move(constraints);
2015-01-30 18:44:25 +00:00
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;
2015-11-20 19:03:57 +00:00
auto *content = pVtab->content;
pVtab->instance->addAffectedTable(content);
2015-01-30 18:44:25 +00:00
pCur->row = 0;
2015-11-20 19:03:57 +00:00
pCur->n = 0;
QueryContext context(content);
2015-01-30 18:44:25 +00:00
2015-11-20 19:03:57 +00:00
for (size_t i = 0; i < content->columns.size(); ++i) {
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.
context.constraints[std::get<0>(content->columns[i])].affinity =
std::get<1>(content->columns[i]);
2015-11-20 19:03:57 +00:00
}
// Filtering between cursors happens iteratively, not consecutively.
// If there are multiple sets of constraints, they apply to each cursor.
#if defined(DEBUG)
plan("Filtering called for table: " + content->name + " [constraint_count=" +
std::to_string(content->constraints.size()) + " argc=" +
std::to_string(argc) + " idx=" + std::to_string(idxNum) + "]");
#endif
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-11-20 19:03:57 +00:00
if (content->constraints.size() > 0) {
auto &constraints = content->constraints[idxNum];
2015-11-20 19:03:57 +00:00
if (argc > 0) {
for (size_t i = 0; i < static_cast<size_t>(argc); ++i) {
auto expr = (const char *)sqlite3_value_text(argv[i]);
if (expr == nullptr || expr[0] == 0) {
// SQLite did not expose the expression value.
continue;
}
// Set the expression from SQLite's now-populated argv.
auto &constraint = constraints[i];
constraint.second.expr = std::string(expr);
plan("Adding constraint to cursor (" + std::to_string(pCur->id) +
"): " + constraint.first + " " + opString(constraint.second.op) +
" " + constraint.second.expr);
2015-11-20 19:03:57 +00:00
// Add the constraint to the column-sorted query request map.
context.constraints[constraint.first].add(constraint.second);
}
2015-11-20 19:03:57 +00:00
} else if (constraints.size() > 0) {
// Constraints failed.
}
2015-01-30 18:44:25 +00:00
}
2015-08-17 23:58:54 +00:00
// Reset the virtual table contents.
2015-11-20 19:03:57 +00:00
pCur->data.clear();
2015-08-17 23:58:54 +00:00
// Generate the row data set.
plan("Scanning rows for cursor (" + std::to_string(pCur->id) + ")");
Registry::callTable(pVtab->content->name, context, pCur->data);
2015-03-18 19:01:58 +00:00
2015-08-17 23:58:54 +00:00
// Set the number of rows.
2015-11-20 19:03:57 +00:00
pCur->n = pCur->data.size();
2015-01-30 18:44:25 +00:00
return SQLITE_OK;
}
}
}
2015-01-30 18:44:25 +00:00
Status attachTableInternal(const std::string &name,
const std::string &statement,
const SQLiteDBInstanceRef &instance) {
if (SQLiteDBManager::isDisabled(name)) {
VLOG(1) << "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::sqlite::xCreate,
tables::sqlite::xCreate,
tables::sqlite::xBestIndex,
tables::sqlite::xDestroy,
tables::sqlite::xDestroy,
tables::sqlite::xOpen,
tables::sqlite::xClose,
tables::sqlite::xFilter,
tables::sqlite::xNext,
tables::sqlite::xEof,
tables::sqlite::xColumn,
tables::sqlite::xRowid,
nullptr, /* Update */
nullptr, /* Begin */
nullptr, /* Sync */
nullptr, /* Commit */
nullptr, /* Rollback */
nullptr, /* FindFunction */
nullptr, /* Rename */
nullptr, /* Savepoint */
nullptr, /* Release */
nullptr, /* RollbackTo */
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.
2016-03-20 23:05:13 +00:00
WriteLock lock(kAttachMutex);
int rc = sqlite3_create_module(instance->db(), name.c_str(), &module,
(void *)&(*instance));
if (rc == SQLITE_OK || rc == SQLITE_MISUSE) {
auto format =
"CREATE VIRTUAL TABLE temp." + name + " USING " + name + statement;
rc = sqlite3_exec(instance->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) {
2016-03-20 23:05:13 +00:00
WriteLock lock(kAttachMutex);
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
}
2016-03-20 23:05:13 +00:00
Status attachFunctionInternal(
const std::string &name,
std::function<
void(sqlite3_context *context, int argc, sqlite3_value **argv)> func) {
// Hold the manager connection instance again in callbacks.
auto dbc = SQLiteDBManager::get();
// Add some shell-specific functions to the instance.
WriteLock lock(kAttachMutex);
int rc = sqlite3_create_function(
dbc->db(),
name.c_str(),
0,
SQLITE_UTF8,
nullptr,
*func.target<void (*)(sqlite3_context *, int, sqlite3_value **)>(),
nullptr,
nullptr);
return Status(rc);
}
void attachVirtualTables(const SQLiteDBInstanceRef &instance) {
if (FLAGS_enable_foreign) {
registerForeignTables();
}
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, instance);
2014-12-03 04:36:46 +00:00
}
}
}
}