/src/osquery/plugins/database/sqlite.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * Copyright (c) 2014-present, The osquery authors |
3 | | * |
4 | | * This source code is licensed as defined by the LICENSE file found in the |
5 | | * root directory of this source tree. |
6 | | * |
7 | | * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only) |
8 | | */ |
9 | | |
10 | | #include <sstream> |
11 | | |
12 | | #include <sqlite3.h> |
13 | | #include <sys/stat.h> |
14 | | |
15 | | #include <osquery/core/query.h> |
16 | | #include <osquery/filesystem/fileops.h> |
17 | | #include <osquery/filesystem/filesystem.h> |
18 | | #include <osquery/logger/logger.h> |
19 | | #include <osquery/utils/conversions/tryto.h> |
20 | | #include <plugins/database/sqlite.h> |
21 | | |
22 | | namespace osquery { |
23 | | |
24 | | const std::map<std::string, std::string> kDBSettings = { |
25 | | {"synchronous", "OFF"}, |
26 | | {"count_changes", "OFF"}, |
27 | | {"default_temp_store", "2"}, |
28 | | {"auto_vacuum", "FULL"}, |
29 | | {"journal_mode", "OFF"}, |
30 | | {"cache_size", "1000"}, |
31 | | {"page_count", "1000"}, |
32 | | }; |
33 | | |
34 | 0 | Status SQLiteDatabasePlugin::setUp() { |
35 | 0 | if (!allowOpen()) { |
36 | 0 | LOG(WARNING) << RLOG(1629) << "Not allowed to set up database plugin"; |
37 | 0 | } |
38 | | |
39 | | // Consume the current settings. |
40 | | // A configuration update may change them, but that does not affect state. |
41 | 0 | path_ = FLAGS_database_path; |
42 | |
|
43 | 0 | if (pathExists(path_).ok() && !isReadable(path_).ok()) { |
44 | 0 | return Status(1, "Cannot read database path: " + path_); |
45 | 0 | } |
46 | | |
47 | 0 | if (!checkingDB()) { |
48 | 0 | VLOG(1) << "Opening database handle: " << path_; |
49 | 0 | } |
50 | | |
51 | | // Tests may trash calls to setUp, make sure subsequent calls do not leak. |
52 | 0 | close(); |
53 | | |
54 | | // Open the SQLite backing storage at path_ |
55 | 0 | auto result = sqlite3_open_v2( |
56 | 0 | path_.c_str(), |
57 | 0 | &db_, |
58 | 0 | (SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE), |
59 | 0 | nullptr); |
60 | |
|
61 | 0 | if (result != SQLITE_OK || db_ == nullptr) { |
62 | 0 | close(); |
63 | | // A failed open in R/W mode is a runtime error. |
64 | 0 | return Status(1, "Cannot open database: " + std::to_string(result)); |
65 | 0 | } |
66 | | |
67 | 0 | for (const auto& domain : kDomains) { |
68 | 0 | std::string q = "create table if not exists " + domain + |
69 | 0 | " (key TEXT PRIMARY KEY, value TEXT);"; |
70 | 0 | result = sqlite3_exec(db_, q.c_str(), nullptr, nullptr, nullptr); |
71 | 0 | if (result != SQLITE_OK) { |
72 | 0 | close(); |
73 | 0 | return Status(1, "Cannot create domain: " + domain); |
74 | 0 | } |
75 | 0 | } |
76 | | |
77 | 0 | std::string settings; |
78 | 0 | for (const auto& setting : kDBSettings) { |
79 | 0 | settings += "PRAGMA " + setting.first + "=" + setting.second + "; "; |
80 | 0 | } |
81 | 0 | sqlite3_exec(db_, settings.c_str(), nullptr, nullptr, nullptr); |
82 | | |
83 | | // RocksDB may not create/append a directory with acceptable permissions. |
84 | 0 | if (platformSetSafeDbPerms(path_) == false) { |
85 | 0 | close(); |
86 | 0 | return Status(1, "Cannot set permissions on database path: " + path_); |
87 | 0 | } |
88 | 0 | return Status(0); |
89 | 0 | } |
90 | | |
91 | 2 | void SQLiteDatabasePlugin::close() { |
92 | 2 | WriteLock lock(close_mutex_); |
93 | 2 | if (db_ != nullptr) { |
94 | 0 | sqlite3_close(db_); |
95 | 0 | db_ = nullptr; |
96 | 0 | } |
97 | 2 | } |
98 | | |
99 | 0 | static int getData(void* argument, int argc, char* argv[], char* column[]) { |
100 | 0 | if (argument == nullptr) { |
101 | 0 | return SQLITE_MISUSE; |
102 | 0 | } |
103 | | |
104 | 0 | QueryData* qData = (QueryData*)argument; |
105 | 0 | Row r; |
106 | 0 | for (int i = 0; i < argc; i++) { |
107 | 0 | if (column[i] != nullptr) { |
108 | 0 | r[column[i]] = (argv[i] != nullptr) ? argv[i] : ""; |
109 | 0 | } |
110 | 0 | } |
111 | 0 | (*qData).push_back(std::move(r)); |
112 | 0 | return 0; |
113 | 0 | } |
114 | | |
115 | | Status SQLiteDatabasePlugin::get(const std::string& domain, |
116 | | const std::string& key, |
117 | 0 | std::string& value) const { |
118 | 0 | QueryData results; |
119 | 0 | char* err = nullptr; |
120 | 0 | std::string q = "select value from " + domain + " where key = '" + key + "';"; |
121 | 0 | sqlite3_exec(db_, q.c_str(), getData, &results, &err); |
122 | 0 | if (err != nullptr) { |
123 | 0 | sqlite3_free(err); |
124 | 0 | } |
125 | | |
126 | | // Only assign value if the query found a result. |
127 | 0 | if (results.size() > 0) { |
128 | 0 | value = std::move(results[0]["value"]); |
129 | 0 | return Status(0); |
130 | 0 | } |
131 | 0 | return Status(1); |
132 | 0 | } |
133 | | |
134 | | Status SQLiteDatabasePlugin::get(const std::string& domain, |
135 | | const std::string& key, |
136 | 0 | int& value) const { |
137 | 0 | std::string result; |
138 | 0 | auto s = this->get(domain, key, result); |
139 | 0 | if (s.ok()) { |
140 | 0 | auto expectedValue = tryTo<int>(result); |
141 | 0 | if (expectedValue.isError()) { |
142 | 0 | return Status::failure("Could not deserialize str to int"); |
143 | 0 | } else { |
144 | 0 | value = expectedValue.take(); |
145 | 0 | } |
146 | 0 | } |
147 | 0 | return s; |
148 | 0 | } |
149 | | |
150 | 0 | static void tryVacuum(sqlite3* db) { |
151 | 0 | std::string q = |
152 | 0 | "SELECT (sum(s1.pageno + 1 == s2.pageno) * 1.0 / count(*)) < 0.01 as v " |
153 | 0 | " FROM " |
154 | 0 | "(SELECT pageno FROM dbstat ORDER BY path) AS s1," |
155 | 0 | "(SELECT pageno FROM dbstat ORDER BY path) AS s2 WHERE " |
156 | 0 | "s1.rowid + 1 = s2.rowid; "; |
157 | |
|
158 | 0 | QueryData results; |
159 | 0 | sqlite3_exec(db, q.c_str(), getData, &results, nullptr); |
160 | 0 | if (results.size() > 0 && results[0]["v"].back() == '1') { |
161 | 0 | sqlite3_exec(db, "vacuum;", nullptr, nullptr, nullptr); |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | Status SQLiteDatabasePlugin::put(const std::string& domain, |
166 | | const std::string& key, |
167 | 0 | const std::string& value) { |
168 | 0 | return putBatch(domain, {std::make_pair(key, value)}); |
169 | 0 | } |
170 | | |
171 | | Status SQLiteDatabasePlugin::put(const std::string& domain, |
172 | | const std::string& key, |
173 | 0 | int value) { |
174 | 0 | return putBatch(domain, {std::make_pair(key, std::to_string(value))}); |
175 | 0 | } |
176 | | |
177 | | Status SQLiteDatabasePlugin::putBatch(const std::string& domain, |
178 | 0 | const DatabaseStringValueList& data) { |
179 | | // Prepare the query, adding placeholders for all the rows we have in `data` |
180 | 0 | std::stringstream buffer; |
181 | 0 | buffer << "insert or replace into " + domain + " values "; |
182 | |
|
183 | 0 | for (auto i = 1U; i <= data.size(); i++) { |
184 | 0 | auto index = i * 2; |
185 | 0 | buffer << "(?" << index - 1 << ", ?" << index << ")"; |
186 | |
|
187 | 0 | if (i + 1 > data.size()) { |
188 | 0 | buffer << ";"; |
189 | 0 | } else { |
190 | 0 | buffer << ", "; |
191 | 0 | } |
192 | 0 | } |
193 | |
|
194 | 0 | const auto& q = buffer.str(); |
195 | | |
196 | | // Bind each value from the rows we got |
197 | 0 | sqlite3_stmt* stmt = nullptr; |
198 | 0 | sqlite3_prepare_v2(db_, q.c_str(), -1, &stmt, nullptr); |
199 | |
|
200 | 0 | { |
201 | 0 | int i = 1; |
202 | |
|
203 | 0 | for (const auto& p : data) { |
204 | 0 | const auto& key = p.first; |
205 | 0 | const auto& value = p.second; |
206 | |
|
207 | 0 | sqlite3_bind_text(stmt, i, key.c_str(), -1, SQLITE_STATIC); |
208 | 0 | sqlite3_bind_text(stmt, i + 1, value.c_str(), -1, SQLITE_STATIC); |
209 | |
|
210 | 0 | i += 2; |
211 | 0 | } |
212 | 0 | } |
213 | |
|
214 | 0 | auto rc = sqlite3_step(stmt); |
215 | 0 | if (rc != SQLITE_DONE) { |
216 | 0 | return Status(1); |
217 | 0 | } |
218 | | |
219 | 0 | sqlite3_finalize(stmt); |
220 | 0 | if (rand() % 10 == 0) { |
221 | 0 | tryVacuum(db_); |
222 | 0 | } |
223 | |
|
224 | 0 | return Status::success(); |
225 | 0 | } |
226 | | |
227 | | Status SQLiteDatabasePlugin::remove(const std::string& domain, |
228 | 0 | const std::string& key) { |
229 | 0 | sqlite3_stmt* stmt = nullptr; |
230 | 0 | std::string q = "delete from " + domain + " where key IN (?1);"; |
231 | 0 | sqlite3_prepare_v2(db_, q.c_str(), -1, &stmt, nullptr); |
232 | |
|
233 | 0 | sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_STATIC); |
234 | 0 | auto rc = sqlite3_step(stmt); |
235 | 0 | if (rc != SQLITE_DONE) { |
236 | 0 | return Status(1); |
237 | 0 | } |
238 | | |
239 | 0 | sqlite3_finalize(stmt); |
240 | 0 | if (rand() % 10 == 0) { |
241 | 0 | tryVacuum(db_); |
242 | 0 | } |
243 | 0 | return Status(0); |
244 | 0 | } |
245 | | |
246 | | Status SQLiteDatabasePlugin::removeRange(const std::string& domain, |
247 | | const std::string& low, |
248 | 0 | const std::string& high) { |
249 | 0 | if (low > high) { |
250 | 0 | return Status::failure("Invalid range: low > high"); |
251 | 0 | } |
252 | | |
253 | 0 | sqlite3_stmt* stmt = nullptr; |
254 | 0 | std::string q = "delete from " + domain + " where key >= ?1 and key <= ?2;"; |
255 | 0 | sqlite3_prepare_v2(db_, q.c_str(), -1, &stmt, nullptr); |
256 | |
|
257 | 0 | sqlite3_bind_text(stmt, 1, low.c_str(), -1, SQLITE_STATIC); |
258 | 0 | sqlite3_bind_text(stmt, 2, high.c_str(), -1, SQLITE_STATIC); |
259 | 0 | auto rc = sqlite3_step(stmt); |
260 | 0 | if (rc != SQLITE_DONE) { |
261 | 0 | return Status(1); |
262 | 0 | } |
263 | | |
264 | 0 | sqlite3_finalize(stmt); |
265 | 0 | if (rand() % 10 == 0) { |
266 | 0 | tryVacuum(db_); |
267 | 0 | } |
268 | 0 | return Status(0); |
269 | 0 | } |
270 | | |
271 | | Status SQLiteDatabasePlugin::scan(const std::string& domain, |
272 | | std::vector<std::string>& results, |
273 | | const std::string& prefix, |
274 | 0 | uint64_t max) const { |
275 | 0 | QueryData _results; |
276 | 0 | char* err = nullptr; |
277 | |
|
278 | 0 | std::string q = |
279 | 0 | "select key from " + domain + " where key LIKE '" + prefix + "%'"; |
280 | 0 | if (max > 0) { |
281 | 0 | q += " limit " + std::to_string(max); |
282 | 0 | } |
283 | 0 | sqlite3_exec(db_, q.c_str(), getData, &_results, &err); |
284 | 0 | if (err != nullptr) { |
285 | 0 | sqlite3_free(err); |
286 | 0 | } |
287 | | |
288 | | // Only assign value if the query found a result. |
289 | 0 | for (auto& r : _results) { |
290 | 0 | results.push_back(std::move(r["key"])); |
291 | 0 | } |
292 | |
|
293 | 0 | return Status::success(); |
294 | 0 | } |
295 | | } // namespace osquery |