/
tables.h
977 lines (842 loc) · 33.6 KB
/
tables.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under both the Apache 2.0 license (found in the
* LICENSE file in the root directory of this source tree) and the GPLv2 (found
* in the COPYING file in the root directory of this source tree).
* You may select, at your option, one of the above-listed licenses.
*/
#pragma once
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#ifdef WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#endif
#include <boost/core/ignore_unused.hpp>
#include <boost/coroutine2/coroutine.hpp>
#include <boost/optional.hpp>
#include <osquery/core.h>
#include <osquery/plugin.h>
#include <osquery/query.h>
/// Allow Tables to use "tracked" deprecated OS APIs.
#define OSQUERY_USE_DEPRECATED(expr) \
do { \
_Pragma("clang diagnostic push") _Pragma( \
"clang diagnostic ignored \"-Wdeprecated-declarations\"")(expr); \
_Pragma("clang diagnostic pop") \
} while (0)
namespace osquery {
class Status;
/**
* @brief osquery does not yet use a NULL type.
*
* If a column type is non-TEXT a NULL is defined as an empty result. The APIs
* may later define an explicit control set that is opaque to the table
* implementation.
*/
#define SQL_NULL_RESULT ""
/**
* @brief The SQLite type affinities are available as macros
*
* Type affinities: TEXT, INTEGER, BIGINT
*
* You can represent any data that can be lexically casted to a string.
* Using the type affinity names helps table developers understand the data
* types they are storing, and more importantly how they are treated at query
* time.
*/
template <typename Type>
inline std::string __sqliteField(const Type& source) noexcept {
return std::to_string(source);
}
template <size_t N>
inline std::string __sqliteField(const char (&source)[N]) noexcept {
return std::string(source, N - 1U);
}
template <size_t N>
inline std::string __sqliteField(const unsigned char (&source)[N]) noexcept {
return std::string(reinterpret_cast<const char*>(source), N - 1U);
}
inline std::string __sqliteField(const char* source) noexcept {
return std::string(source);
}
inline std::string __sqliteField(char* const source) noexcept {
return std::string(source);
}
inline std::string __sqliteField(const unsigned char* source) noexcept {
return std::string(reinterpret_cast<const char*>(source));
}
inline std::string __sqliteField(unsigned char* const source) noexcept {
return std::string(reinterpret_cast<char* const>(source));
}
inline std::string __sqliteField(const std::string& source) noexcept {
return source;
}
#ifdef WIN32
// TEXT is also defined in windows.h, we should not re-define it
#define SQL_TEXT(x) __sqliteField(x)
#else
#define SQL_TEXT(x) __sqliteField(x)
#define TEXT(x) __sqliteField(x)
#endif
/// See the affinity type documentation for TEXT.
#define INTEGER(x) __sqliteField(x)
/// See the affinity type documentation for TEXT.
#define BIGINT(x) __sqliteField(x)
/// See the affinity type documentation for TEXT.
#define UNSIGNED_BIGINT(x) __sqliteField(x)
/// See the affinity type documentation for TEXT.
#define DOUBLE(x) __sqliteField(x)
/**
* @brief The SQLite type affinities as represented as implementation literals.
*
* Type affinities: TEXT=std::string, INTEGER=int, BIGINT=long long int
*
* Just as the SQLite data is represented as lexically casted strings, as table
* may make use of the implementation language literals.
*/
#define TEXT_LITERAL std::string
/// See the literal type documentation for TEXT_LITERAL.
#define INTEGER_LITERAL int
/// See the literal type documentation for TEXT_LITERAL.
#define BIGINT_LITERAL int64_t
/// See the literal type documentation for TEXT_LITERAL.
#define UNSIGNED_BIGINT_LITERAL uint64_t
/// See the literal type documentation for TEXT_LITERAL.
#define DOUBLE_LITERAL double
enum ColumnType {
UNKNOWN_TYPE = 0,
TEXT_TYPE,
INTEGER_TYPE,
BIGINT_TYPE,
UNSIGNED_BIGINT_TYPE,
DOUBLE_TYPE,
BLOB_TYPE,
};
/// Map of type constant to the SQLite string-name representation.
extern const std::map<ColumnType, std::string> kColumnTypeNames;
/**
* @brief A ConstraintOperator is applied in an query predicate.
*
* If the query contains a join or where clause with a constraint operator and
* expression the table generator may limit the data appropriately.
*/
enum ConstraintOperator : unsigned char {
EQUALS = 2,
GREATER_THAN = 4,
LESS_THAN_OR_EQUALS = 8,
LESS_THAN = 16,
GREATER_THAN_OR_EQUALS = 32,
MATCH = 64,
LIKE = 65,
GLOB = 66,
REGEXP = 67,
UNIQUE = 1,
};
/// Type for flags for what constraint operators are admissible.
using ConstraintOperatorFlag = unsigned char;
/// Flag for any operator type.
#define ANY_OP 0xFFU
/**
* @brief A Constraint is an operator and expression.
*
* The constraint is applied to columns which have literal and affinity types.
*/
struct Constraint {
unsigned char op;
std::string expr;
/// Construct a Constraint with the most-basic information, the operator.
explicit Constraint(unsigned char _op) {
op = _op;
}
// A constraint list in a context knows only the operator at creation.
explicit Constraint(unsigned char _op, std::string _expr)
: op(_op), expr(std::move(_expr)) {}
};
/*
* @brief Column options allow for more-complicated modeling of concepts.
*
* To accommodate the oddities of operating system concepts we make use of
* simple SQLite abstractions like indexs/keys and foreign keys, we also
* allow for optimizing based on query constraints (WHERE).
*
* There are several 'complications' where the default table filter (SELECT)
* behavior attempts to mimic reality. Browser plugins or shell history are
* good examples, a SELECT without using a WHERE returns the plugins or
* history as it applies to the user running the query. If osquery is meant
* to be a daemon with absolute visibility this introduces an abnormality,
* as the expected result will only include the superuser's view, even if
* the superuser can view everything if they intended.
*
* The solution is to explicitly ask for everything, by joining against the
* users table. This options structure will allow the table implementations
* to communicate these subtleties to the user.
*/
enum class ColumnOptions {
/// Default/no options.
DEFAULT = 0,
/// Treat this column as a primary key.
INDEX = 1,
/// This column MUST be included in the query predicate.
REQUIRED = 2,
/*
* @brief This column is used to generate additional information.
*
* If this column is included in the query predicate, the table will generate
* additional information. Consider the browser_plugins or shell history
* tables: by default they list the plugins or history relative to the user
* running the query. However, if the calling query specifies a UID explicitly
* in the predicate, the meaning of the table changes and results for that
* user are returned instead.
*/
ADDITIONAL = 4,
/*
* @brief This column can be used to optimize the query.
*
* If this column is included in the query predicate, the table will generate
* optimized information. Consider the system_controls table, a default filter
* without a query predicate lists all of the keys. When a specific domain is
* included in the predicate then the table will only issue syscalls/lookups
* for that domain, greatly optimizing the time and utilization.
*
* This optimization does not mean the column is an index.
*/
OPTIMIZED = 8,
/// This column should be hidden from '*'' selects.
HIDDEN = 16,
};
/// Treat column options as a set of flags.
inline ColumnOptions operator|(ColumnOptions a, ColumnOptions b) {
return static_cast<ColumnOptions>(static_cast<int>(a) | static_cast<int>(b));
}
/// Treat column options as a set of flags.
inline size_t operator&(ColumnOptions a, ColumnOptions b) {
return static_cast<size_t>(a) & static_cast<size_t>(b);
}
/**
* @brief Attributes about a Table implementation.
*/
enum class TableAttributes {
/// Default no-op attribute.
NONE = 0,
/// This table is a 'utility' and is always available locally.
UTILITY = 1,
/// The results from this table may be cached within a schedule.
CACHEABLE = 2,
/// The results are backed by a set time-indexed, always growing, events.
EVENT_BASED = 4,
/// This table inspect items relative to each user, a JOIN may be required.
USER_BASED = 8,
/// This table's data requires an osquery kernel extension/module.
KERNEL_REQUIRED = 16,
};
/// Treat table attributes as a set of flags.
inline TableAttributes operator|(TableAttributes a, TableAttributes b) {
return static_cast<TableAttributes>(static_cast<int>(a) |
static_cast<int>(b));
}
/// Treat column options as a set of flags.
inline size_t operator&(TableAttributes a, TableAttributes b) {
return static_cast<size_t>(a) & static_cast<size_t>(b);
}
/// Helper alias for TablePlugin names.
using TableName = std::string;
/// Alias for an ordered list of column name and corresponding SQL type.
using TableColumns =
std::vector<std::tuple<std::string, ColumnType, ColumnOptions>>;
/// Alias for map of column alias sets.
using ColumnAliasSet = std::map<std::string, std::set<std::string>>;
/// Forward declaration of QueryContext for ConstraintList relationships.
struct QueryContext;
/**
* @brief A ConstraintList is a set of constraints for a column. This list
* should be mapped to a left-hand-side column name.
*
* The table generator does not need to check each constraint in its decision
* logic. The common constraint checking patterns (match) are abstracted using
* simple logic operators on the literal SQLite affinity types.
*
* A constraint list supports all AS_LITERAL types, and all ConstraintOperators.
*/
struct ConstraintList : private boost::noncopyable {
public:
/// The SQLite affinity type.
ColumnType affinity{TEXT_TYPE};
/**
* @brief Check if an expression matches the query constraints.
*
* Evaluate ALL constraints in this ConstraintList against the string
* expression. The affinity of the constraint will be used as the affinite
* and lexical type of the expression and set of constraint expressions.
* If there are no predicate constraints in this list, all expression will
* match. Constraints are limitations.
*
* @param expr a SQL type expression of the column literal type to check.
* @return If the expression matched all constraints.
*/
bool matches(const std::string& expr) const;
/**
* @brief Check if an expression matches the query constraints.
*
* `matches` also supports the set of SQL affinite types.
* The expression expr will be evaluated as a string and compared using
* the affinity of the constraint.
*
* @param expr a SQL type expression of the column literal type to check.
* @return If the expression matched all constraints.
*/
template <typename T>
bool matches(const T& expr) const {
return matches(SQL_TEXT(expr));
}
/**
* @brief Check and return if there are constraints on this column.
*
* A ConstraintList is used in a ConstraintMap with a column name as the
* map index. Tables that act on optional constraints should check if any
* constraint was provided. The ops parameter serves to specify which
* operators we want to check existence for.
*
* @param ops (Optional: default ANY_OP) The operators types to look for.
* @return true if any constraint exists.
*/
bool exists(ConstraintOperatorFlag ops = ANY_OP) const;
/**
* @brief Check if a constraint exists AND matches the type expression.
*
* See ConstraintList::exists and ConstraintList::matches.
*
* @param expr The expression to match.
* @return true if any constraint exists AND matches the type expression.
*/
template <typename T>
bool existsAndMatches(const T& expr) const {
return (exists() && matches(expr));
}
/**
* @brief Check if a constraint is missing or matches a type expression.
*
* A ConstraintList is used in a ConstraintMap with a column name as the
* map index. Tables that act on required constraints can make decisions
* on missing constraints or a constraint match.
*
* @param expr The expression to match.
* @return true if constraint is missing or matches the type expression.
*/
template <typename T>
bool notExistsOrMatches(const T& expr) const {
return (!exists() || matches(expr));
}
/**
* @brief Helper templated function for ConstraintList::matches.
*/
template <typename T>
bool literal_matches(const T& base_expr) const;
/**
* @brief Get all expressions for a given ConstraintOperator.
*
* This is most useful if the table generation requires as column.
* The generator may `getAll(EQUALS)` then iterate.
*
* @param op the ConstraintOperator.
* @return A list of TEXT%-represented types matching the operator.
*/
std::set<std::string> getAll(ConstraintOperator op) const;
/// See ConstraintList::getAll, but as a selected literal type.
template <typename T>
std::set<T> getAll(ConstraintOperator op) const;
/// Constraint list accessor, types and operator.
const std::vector<struct Constraint>& getAll() const {
return constraints_;
}
/**
* @brief Add a new Constraint to the list of constraints.
*
* @param constraint a new operator/expression to constrain.
*/
void add(const struct Constraint& constraint) {
constraints_.push_back(constraint);
}
/**
* @brief Serialize a ConstraintList into a property tree.
*
* The property tree will use the format:
* {
* "affinity": affinity,
* "list": [
* {"op": op, "expr": expr}, ...
* ]
* }
*/
void serialize(JSON& doc, rapidjson::Value& obj) const;
/// See ConstraintList::unserialize.
void deserialize(const rapidjson::Value& obj);
private:
/// List of constraint operator/expressions.
std::vector<struct Constraint> constraints_;
private:
friend struct QueryContext;
private:
FRIEND_TEST(TablesTests, test_constraint_list);
};
/// Pass a constraint map to the query request.
using ConstraintMap = std::map<std::string, struct ConstraintList>;
/// Populate a constraint list from a query's parsed predicate.
using ConstraintSet = std::vector<std::pair<std::string, struct Constraint>>;
/// Keep track of which columns are used
using UsedColumns = std::unordered_set<std::string>;
/**
* @brief osquery table content descriptor.
*
* This object is the abstracted SQLite database's virtual table descriptor.
* When the virtual table is created/connected the name and columns are
* retrieved via the TablePlugin call API. The details are kept in this context
* so column parsing and row walking does not require additional Registry calls.
*
* When tables are accessed as the result of an SQL statement a QueryContext is
* created to represent metadata that can be used by the virtual table
* implementation code. Thus the code that generates rows can choose to emit
* additional data, restrict based on constraints, or potentially yield from
* a cache or choose not to generate certain columns.
*/
struct VirtualTableContent {
/// Friendly name for the table.
TableName name;
/// Table column structure, retrieved once via the TablePlugin call API.
TableColumns columns;
/// Attributes are copied into the content such that they can be quickly
/// passed to the SQL and optional Query for inspection.
TableAttributes attributes{TableAttributes::NONE};
/**
* @brief Table column aliases structure.
*
* This is used within xColumn to move content from special HIDDEN columns
* that act as aliases. If these columns are requested the content is moved
* from the new non-deprecated name.
*/
std::map<std::string, size_t> aliases;
/// Transient set of virtual table access constraints.
std::unordered_map<size_t, ConstraintSet> constraints;
/// Transient set of virtual table used columns
std::unordered_map<size_t, UsedColumns> colsUsed;
/*
* @brief A table implementation specific query result cache.
*
* Virtual tables may 'cache' information between filter requests. This is
* intended to provide optimization for very latent/expensive tables where
* complex joins may result in duplicate filter requests.
*
* The cache is implemented as a map of row data. The cache concept
* should utilize a primary key as an index, and may store arbitrary data.
* More intense caching may use the backing store though the general database
* set and get calls.
*
* The in-memory, non-backing store, cache is expired after each query run.
* This caching does not affect or use the schedule results cache.
*/
std::map<std::string, Row> cache;
};
using RowGenerator = boost::coroutines2::coroutine<Row&>;
using RowYield = RowGenerator::push_type;
/**
* @brief A QueryContext is provided to every table generator for optimization
* on query components like predicate constraints and limits.
*/
struct QueryContext : private only_movable {
/// Construct a context without cache support.
QueryContext() : table_(new VirtualTableContent()) {}
/// If the context was created without content, it is ephemeral.
~QueryContext() {
if (!enable_cache_ && table_ != nullptr) {
delete table_;
table_ = nullptr;
}
}
/// Construct a context and set the table content for caching.
explicit QueryContext(VirtualTableContent* content)
: enable_cache_(true), table_(content) {}
/// Allow moving.
QueryContext(QueryContext&&) = default;
/// Allow move assignment.
QueryContext& operator=(QueryContext&&) = delete;
/**
* @brief Check if a constraint exists for a given column operator pair.
*
* Operator and expression existence and matching occurs on the constraint
* list for a given column name. The query context maintains a map of columns
* to potentially empty constraint lists. Check if a constraint exists with
* any operator or for a specific operator, usually equality (EQUALS).
*
* @param column The name of a column within this table.
* @param op Check for a specific constraint operator (default EQUALS).
* @return true if a constraint exists, false if empty or no operator match.
*/
bool hasConstraint(const std::string& column,
ConstraintOperator op = EQUALS) const;
/**
* @brief Apply a predicate function to each expression in a constraint list.
*
* Most constraint sets are use to extract expressions or perform a row
* generation for each expressions (given an operator).
*
* This prevents the caller (table implementation) from extracting the set
* and iterating separately on potentially duplicate and copied data. The
* predicate function is provided two arguments:
* - An iterating reference to each expression for the given operator.
*
* @param column The name of a column within this table.
* @param op The comparison or expression operator (e.g., EQUALS).
* @param predicate A predicate receiving each expression.
*/
template <typename T>
void iteritems(const std::string& column,
ConstraintOperator op,
std::function<void(const T& expr)> predicate) const {
if (constraints.count(column) > 0) {
const auto& list = constraints.at(column);
if (list.affinity == TEXT_TYPE) {
for (const auto& constraint : list.constraints_) {
if (constraint.op == op) {
predicate(constraint.expr);
}
}
} else {
auto constraint_set = list.getAll<T>(op);
for (const auto& constraint : constraint_set) {
predicate(constraint);
}
}
}
}
/// Helper for string type (most all types are TEXT/VARCHAR).
void iteritems(const std::string& column,
ConstraintOperator op,
std::function<void(const std::string& expr)> predicate) const {
return iteritems<std::string>(column, op, std::move(predicate));
}
/**
* @brief Expand a list of constraints into a set of values.
*
* This is most (perhaps only) helpful with filesystem globbing inputs.
* The requirement is a constraint column that takes an expandable input.
* This method will accept an expand predicate and return the aggregate set of
* expanded items.
*
* In the future this will be a templated type that restricts the predicate
* to act on the column's affinite type and returns a similar-typed set.
*
* @param column The name of a column within this table.
* @param op An operator to retrieve from the constraint list.
* @param output The output parameter, a set of expanded values.
* @param predicate A predicate lambda to apply to each constraint.
* @return An aggregate status, if any predicate fails the operation fails.
*/
Status expandConstraints(
const std::string& column,
ConstraintOperator op,
std::set<std::string>& output,
std::function<Status(const std::string& constraint,
std::set<std::string>& output)> predicate);
/// Check if the given column is used by the query
bool isColumnUsed(const std::string& colName) const;
/// Check if any of the given columns is used by the query
bool isAnyColumnUsed(std::initializer_list<std::string> colNames) const;
template <typename Type>
inline void setTextColumnIfUsed(Row& r,
const std::string& colName,
const Type& value) const {
if (isColumnUsed(colName)) {
r[colName] = TEXT(value);
}
}
template <typename Type>
inline void setIntegerColumnIfUsed(Row& r,
const std::string& colName,
const Type& value) const {
if (isColumnUsed(colName)) {
r[colName] = INTEGER(value);
}
}
template <typename Type>
inline void setBigIntColumnIfUsed(Row& r,
const std::string& colName,
const Type& value) const {
if (isColumnUsed(colName)) {
r[colName] = BIGINT(value);
}
}
inline void setColumnIfUsed(Row& r,
const std::string& colName,
const std::string& value) const {
if (isColumnUsed(colName)) {
r[colName] = value;
}
}
/// Check if a table-defined index exists within the query cache.
bool isCached(const std::string& index) const;
/// Retrieve an index within the query cache.
const Row& getCache(const std::string& index);
/// Helper to retrieve a keyed element within the query cache.
const std::string& getCache(const std::string& index, const std::string& key);
/// Request the context use the warm query cache.
void useCache(bool use_cache);
/// Check if the query requested use of the warm query cache.
bool useCache() const;
/// Set the entire cache for an index.
void setCache(const std::string& index, Row _cache);
/// Helper to set a keyed element within the query cache.
void setCache(const std::string& index,
const std::string& key,
std::string _item);
/// The map of column name to constraint list.
ConstraintMap constraints;
boost::optional<UsedColumns> colsUsed;
private:
/// If false then the context is maintaining an ephemeral cache.
bool enable_cache_{false};
/// If the context is allowed to use the warm query cache.
bool use_cache_{false};
/// Persistent table content for table caching.
VirtualTableContent* table_{nullptr};
private:
friend class TablePlugin;
};
using QueryContext = struct QueryContext;
using Constraint = struct Constraint;
/**
* @brief The TablePlugin defines the name, types, and column information.
*
* To attach a virtual table create a TablePlugin subclass and register the
* virtual table name as the plugin ID. osquery will enumerate all registered
* TablePlugins and attempt to attach them to SQLite at instantiation.
*
* Note: When updating this class, be sure to update the corresponding template
* in osquery/tables/templates/default.cpp.in
*/
class TablePlugin : public Plugin {
public:
/**
* @brief Table name aliases create full-scan VIEWs for tables.
*
* Aliases allow table names to be changed/deprecated without breaking
* existing deployments and scheduled queries.
*
* @return A string vector of qtable name aliases.
*/
virtual std::vector<std::string> aliases() const {
return {};
}
/// Return the table's column name and type pairs.
virtual TableColumns columns() const {
return TableColumns();
}
/// Define a map of target columns to optional aliases.
virtual ColumnAliasSet columnAliases() const {
return ColumnAliasSet();
}
/// Return a set of attribute flags.
virtual TableAttributes attributes() const {
return TableAttributes::NONE;
}
/**
* @brief Generate a complete table representation.
*
* The TablePlugin::generate method is the most important part of the table.
* This should return a best-effort match of the expected results for a
* query. In common cases, this returns all rows for a virtual table.
* For EventSubscriber tables this will perform database lookups for events
* matching several conditions such as time within the SQL query or the last
* time the EventSubscriber was called.
*
* The context input is filled in "as best possible" by SQLite's
* virtual table APIs. In the best case this context include a limit or
* constraints organized by each possible column.
*
* @param context A query context filled in by SQLite's virtual table API.
* @return The result rows for this table, given the query context.
*/
virtual QueryData generate(QueryContext& context) {
(void)context;
return QueryData();
}
/// Callback for DELETE statements
virtual QueryData delete_(QueryContext& context,
const PluginRequest& request) {
boost::ignore_unused(context);
boost::ignore_unused(request);
return {{std::make_pair("status", "readonly")}};
}
/// Callback for INSERT statements
virtual QueryData insert(QueryContext& context,
const PluginRequest& request) {
boost::ignore_unused(context);
boost::ignore_unused(request);
return {{std::make_pair("status", "readonly")}};
}
/// Callback for UPDATE statements
virtual QueryData update(QueryContext& context,
const PluginRequest& request) {
boost::ignore_unused(context);
boost::ignore_unused(request);
return {{std::make_pair("status", "readonly")}};
}
/**
* @brief Generate a table representation by yielding each row.
*
* For tables that set generator=True in their spec's implementation, this
* generator will be bound to an asymmetric coroutine. It should call the
* provided yield function for each Row returned. Treat this like Python's
* generator-type methods where the only difference is yield is not reserved
* but rather provided with some boilerplate syntax.
*
* This implementation uses nearly %5 more cycles than the generate method
* when the table content is small (less than 100 rows) and has a disadvantage
* of not being cachable since the entire contents are not available before
* post-filter aggregations. This implementation prevents the need for
* multiple representations of table content existing simultaneously and is
* always more memory efficient. It can be more compute efficient for tables
* with over 1000 rows.
*
* @param yield a callable that takes a single Row as input.
* @param context a query context filled in by SQLite's virtual table API.
*/
virtual void generator(RowYield& yield, QueryContext& context) {
(void)yield;
(void)context;
}
/// Override and return true to use the generator and yield method.
virtual bool usesGenerator() const {
return false;
}
protected:
/// An SQL table containing the table definition/syntax.
std::string columnDefinition(bool is_extension = false) const;
/// Return the name and column pairs for attaching virtual tables.
PluginResponse routeInfo() const override;
/**
* @brief Check if there are fresh cache results for this table.
*
* Table results are considered fresh when evaluated against a given interval.
* The interval is the expected rate for which this data should be generated.
* Caching and cache freshness only applies to queries acting on tables
* within a schedule. If two queries "one" and "two" both inspect the
* table "processes" at the interval 60. The first executed will cache results
* and the second will use the cached results.
*
* Table results are not cached if a QueryContext contains constraints or
* provides HOB (hand-off blocks) to additional tables within a query.
* Currently, the query scheduler cannot communicate to table implementations.
* An interval is set globally by the scheduler and passed to the table
* implementation as a future-proof API. There is no "shortcut" for caching
* when used in external tables. A cache lookup within an extension means
* a database call API and re-serialization to the virtual table APIs. In
* practice this does not perform well and is explicitly disabled.
*
* @param interval The interval this query expects the tables results.
* @param ctx The query context.
* @return True if the cache contains fresh results, otherwise false.
*/
bool isCached(size_t interval, const QueryContext& ctx) const;
/**
* @brief Perform a database lookup of cached results and deserialize.
*
* If a query determined the table's cached results are fresh, it may ask the
* table to retrieve results from the database and deserialized them into
* table row data.
*
* @return The deserialized row data of cached results.
*/
QueryData getCache() const;
/**
* @brief Similar to getCache, stores the results from generate.
*
* Set will serialize and save the results as JSON to be retrieved later.
* It will inspect the query context, if any required/indexed/optimized or
* additional columns are used then the cache will not be saved.
*/
void setCache(size_t step,
size_t interval,
const QueryContext& ctx,
const QueryData& results);
private:
/// The last time in seconds the table data results were saved to cache.
size_t last_cached_{0};
/// The last interval in seconds when the table data was cached.
size_t last_interval_{0};
public:
/**
* @brief The scheduled interval for the executing query.
*
* Scheduled queries execute within a pseudo-mutex, and each may communicate
* their scheduled interval to internal TablePlugin implementations. If the
* table is cachable then the interval can be used to calculate freshness.
*/
static size_t kCacheInterval;
/// The schedule step, this is the current position of the schedule.
static size_t kCacheStep;
public:
/**
* @brief The registry call "router".
*
* Like all of osquery's Plugin%s, the TablePlugin uses a "call" router to
* handle requests and responses from extensions. The TablePlugin uses an
* "action" key, which can be:
* - generate: call the plugin's row generate method (defined in spec).
* - columns: return a list of column name and SQLite types.
* - definition: return an SQL statement for table creation.
*
* @param request The plugin request, must include an action key.
* @param response A plugin response, for generation this contains the rows.
*/
Status call(const PluginRequest& request, PluginResponse& response) override;
public:
/// Helper data structure transformation methods.
static void setRequestFromContext(const QueryContext& context,
PluginRequest& request);
/// Helper data structure transformation methods.
static void setContextFromRequest(const PluginRequest& request,
QueryContext& context);
public:
/**
* @brief Add a virtual table that exists in an extension.
*
* When external table plugins are registered the core will attach them
* as virtual tables to the SQL internal implementation.
*
* @param name The table name.
* @param info The route info (column name and type pairs).
*/
static Status addExternal(const std::string& name,
const PluginResponse& info);
/// Remove an extension's table from the SQL virtual database.
static void removeExternal(const std::string& name);
private:
friend class RegistryFactory;
FRIEND_TEST(VirtualTableTests, test_tableplugin_columndefinition);
FRIEND_TEST(VirtualTableTests, test_extension_tableplugin_columndefinition);
FRIEND_TEST(VirtualTableTests, test_tableplugin_statement);
FRIEND_TEST(VirtualTableTests, test_indexing_costs);
FRIEND_TEST(VirtualTableTests, test_table_results_cache);
FRIEND_TEST(VirtualTableTests, test_yield_generator);
};
/// Helper method to generate the virtual table CREATE statement.
std::string columnDefinition(const TableColumns& columns,
bool is_extension = false);
/// Helper method to generate the virtual table CREATE statement.
std::string columnDefinition(const PluginResponse& response,
bool aliases = false,
bool is_extension = false);
/// Get the string representation for an SQLite column type.
inline const std::string& columnTypeName(ColumnType type) {
return kColumnTypeNames.at(type);
}
/// Get the column type from the string representation.
ColumnType columnTypeName(const std::string& type);
} // namespace osquery