Sitelet https://github.com/0x09/sqlite-statement-vtab/commit/400bea16a314bb3994c3912a6639f488af98e9e0
Skip to content

Commit 400bea1

Browse files
committed
Encode parameter indexes to a printable string in idxStr
This resolves a potential vulnerability on platforms with uncommon integer widths (see below.) Per the discussion in sqlite.org/forum/info/853f5b586ecbf11c idxStr is strictly intended to reference valid string memory. statement_vtab uses idxStr to supply parameter indexes to xFilter, previously passing these as integer memory directly. It now serializes indexes using a fixed- length 6 bit encoding, which for speed and simplicity is printable but not necessarily readable. idxStr is currently included in EXPLAIN query results as well as in debug output from SQLite. On typical platforms where int is larger than SQLite's column maximum of 32767, previous buffers were incidentally valid for these only due to what amounts to several technicalities in the C standard. On platforms with less common integer widths -- such as those with 16 bit ints or where sizeof(int) is 1 -- there is a risk of overread and disclosure of subsequent memory if an untrusted user is able to execute an EXPLAIN query on a statement_vtab with a sufficient number of columns. Application of this fix can be verified at runtime with the following SQL: > CREATE VIRTUAL TABLE x USING statement((SELECT ?42, ?46)); > EXPLAIN SELECT * FROM x WHERE [46] = 0 AND [42] = 0; In the output of EXPLAIN, the VFilter opcode's P4 register will read "O!!!!!K!!!!!" (without quotes).
1 parent dab1c38 commit 400bea1

1 file changed

Lines changed: 46 additions & 15 deletions

File tree

‎statement_vtab.c‎

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ SQLITE_EXTENSION_INIT1
1010
#include <string.h>
1111
#include <stdio.h>
1212
#include <assert.h>
13+
#include <stdint.h>
14+
#include <limits.h>
1315

1416
struct statement_vtab {
1517
sqlite3_vtab base;
@@ -181,6 +183,26 @@ static int statement_vtab_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx,
181183
return SQLITE_OK;
182184
}
183185

186+
// parameter map encoding for xBestIndex/xFilter
187+
// constraint -> param index mappings are stored in idxStr when not contiguous. idxStr is expected to be NUL terminated
188+
// and printable, so we use a 6 bit encoding in the ASCII range.
189+
// for simplicity encoded indexes are fixed to the length necessary to encode an int. this is overkill on most systems
190+
// due to sqlite's current hard limit on number of columns but makes statement_vtab agnostic to changes to this limit
191+
const static size_t param_idx_size = (sizeof(int)*CHAR_BIT+5)/6;
192+
193+
static inline void encode_param_idx(int i, char* restrict param_map, int param_idx) {
194+
assert(param_idx >= 0);
195+
for(size_t j = 0; j < param_idx_size; j++)
196+
param_map[i*param_idx_size+j] = ((param_idx >> 6*j) & 63) + 33;
197+
}
198+
199+
static inline int decode_param_idx(int i, const char* param_map) {
200+
int param_idx = 0;
201+
for(size_t j = 0; j < param_idx_size; j++)
202+
param_idx |= (param_map[i*param_idx_size+j] - 33) << 6*j;
203+
return param_idx;
204+
}
205+
184206
// xBestIndex needs to communicate which columns are constrained by the where clause to xFilter;
185207
// in terms of a statement table this translates to which parameters will be available to bind.
186208
static int statement_vtab_filter(sqlite3_vtab_cursor* cur, int idxNum, const char* idxStr, int argc, sqlite3_value** argv) {
@@ -191,9 +213,11 @@ static int statement_vtab_filter(sqlite3_vtab_cursor* cur, int idxNum, const cha
191213
sqlite3_clear_bindings(stmt);
192214

193215
int ret;
194-
for(int i = 0; i < argc; i++)
195-
if((ret = sqlite3_bind_value(stmt,idxStr?((int*)idxStr)[i]:i+1,argv[i])) != SQLITE_OK)
216+
for(int i = 0; i < argc; i++) {
217+
int param_idx = idxStr ? decode_param_idx(i,idxStr) : i+1;
218+
if((ret = sqlite3_bind_value(stmt,param_idx,argv[i])) != SQLITE_OK)
196219
return ret;
220+
}
197221
ret = sqlite3_step(stmt);
198222
if(!(ret == SQLITE_ROW || ret == SQLITE_DONE))
199223
return ret;
@@ -237,29 +261,36 @@ static int statement_vtab_best_index(sqlite3_vtab* pVTab, sqlite3_index_info* in
237261
// if the constrained columns are contiguous then we can just tell sqlite to order the arg vector provided to xFilter
238262
// in the same order as our column bindings, so there's no need to map between these
239263
// (this will always be the case when calling the vtab as a table-valued function)
240-
// only support this optimization for up to 64 constrained columns since checking for continuity more generally would cost as much
264+
// only support this optimization for up to 64 constrained columns since checking for continuity more generally would cost nearly as much
241265
// as just allocating the mapping
242266
sqlite_uint64 required_cols = (col_max < 64 ? 1ull << col_max : 0ull)-1;
243267
if(!out_constraints || (col_max <= 64 && used_cols == required_cols))
244268
return SQLITE_OK;
245269

246270
// otherwise map the constraint index as provided to xFilter to column index for bindings
247-
// if this is sparse e.g. where arg1 = x and arg3 = y then we store this separately in idxStr
248-
int* colmap = sqlite3_malloc64(sizeof(*colmap)*out_constraints);
249-
if(!colmap)
250-
return SQLITE_NOMEM;
271+
// this will only be necessary when constraints are not contiguous e.g. where arg1 = x and arg3 = y
272+
// in that case bound parameter indexes are encoded as a string in idxStr, in the order they appear in constriants
273+
if((size_t)out_constraints > (SIZE_MAX-1)/param_idx_size) {
274+
sqlite3_free(pVTab->zErrMsg);
275+
if(!(pVTab->zErrMsg = sqlite3_mprintf("Too many constraints to index: %d",out_constraints)))
276+
return SQLITE_NOMEM;
277+
return SQLITE_ERROR;
278+
}
251279

252-
int argc = 0;
253-
int old_index;
254-
for(int i = 0; i < index_info->nConstraint; i++)
255-
if((old_index = index_info->aConstraintUsage[i].argvIndex)) {
256-
colmap[argc] = old_index;
257-
index_info->aConstraintUsage[i].argvIndex = ++argc;
258-
}
280+
if(!(index_info->idxStr = sqlite3_malloc64(out_constraints*param_idx_size+1)))
281+
return SQLITE_NOMEM;
259282

260-
index_info->idxStr = (char*)colmap;
261283
index_info->needToFreeIdxStr = 1;
262284

285+
for(int i = 0, constraint_idx = 0; i < index_info->nConstraint; i++) {
286+
if(!index_info->aConstraintUsage[i].argvIndex)
287+
continue;
288+
encode_param_idx(constraint_idx,index_info->idxStr,index_info->aConstraintUsage[i].argvIndex);
289+
index_info->aConstraintUsage[i].argvIndex = ++constraint_idx;
290+
}
291+
292+
index_info->idxStr[out_constraints*param_idx_size] = '\0';
293+
263294
return SQLITE_OK;
264295
}
265296

0 commit comments

Comments
 (0)