mirror of
https://github.com/sqlite/sqlite.git
synced 2025-12-24 14:17:58 +03:00
Add the ability to process dbsqlfuzz cases in fuzzcheck and add an initial
set of interesting dbsqlfuzz cases. FossilOrigin-Name: fb9074ff450a67feaa62ca61d19154de26d5c8a8d147409ee6d1fbd667b2914f
This commit is contained in:
475
test/fuzzcheck.c
475
test/fuzzcheck.c
@@ -69,6 +69,7 @@
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include "sqlite3.h"
|
||||
#define ISSPACE(X) isspace((unsigned char)(X))
|
||||
#define ISDIGIT(X) isdigit((unsigned char)(X))
|
||||
@@ -400,7 +401,6 @@ static void blobListFree(Blob *p){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Return the current wall-clock time */
|
||||
static sqlite3_int64 timeOfDay(void){
|
||||
static sqlite3_vfs *clockVfs = 0;
|
||||
@@ -419,6 +419,448 @@ static sqlite3_int64 timeOfDay(void){
|
||||
return t;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
** Code to process combined database+SQL scripts generated by the
|
||||
** dbsqlfuzz fuzzer.
|
||||
*/
|
||||
|
||||
/* An instance of the following object is passed by pointer as the
|
||||
** client data to various callbacks.
|
||||
*/
|
||||
typedef struct FuzzCtx {
|
||||
sqlite3 *db; /* The database connection */
|
||||
sqlite3_int64 iCutoffTime; /* Stop processing at this time. */
|
||||
sqlite3_int64 iLastCb; /* Time recorded for previous progress callback */
|
||||
sqlite3_int64 mxInterval; /* Longest interval between two progress calls */
|
||||
unsigned nCb; /* Number of progress callbacks */
|
||||
unsigned mxCb; /* Maximum number of progress callbacks allowed */
|
||||
unsigned execCnt; /* Number of calls to the sqlite3_exec callback */
|
||||
int timeoutHit; /* True when reaching a timeout */
|
||||
} FuzzCtx;
|
||||
|
||||
/* Verbosity level for the dbsqlfuzz test runner */
|
||||
static int eVerbosity = 0;
|
||||
|
||||
/* True to activate PRAGMA vdbe_debug=on */
|
||||
static int bVdbeDebug = 0;
|
||||
|
||||
/* Timeout for each fuzzing attempt, in milliseconds */
|
||||
static int iTimeout = 10000; /* Defaults to 10 seconds */
|
||||
|
||||
/* Maximum number of progress handler callbacks */
|
||||
static unsigned int mxProgressCb = 2000;
|
||||
|
||||
/* Maximum string length in SQLite */
|
||||
static int lengthLimit = 1000000;
|
||||
|
||||
/* Maximum byte-code program length in SQLite */
|
||||
static int vdbeOpLimit = 25000;
|
||||
|
||||
/* Maximum size of the in-memory database */
|
||||
static sqlite3_int64 maxDbSize = 104857600;
|
||||
|
||||
/*
|
||||
** Translate a single byte of Hex into an integer.
|
||||
** This routine only works if h really is a valid hexadecimal
|
||||
** character: 0..9a..fA..F
|
||||
*/
|
||||
static unsigned int hexToInt(unsigned int h){
|
||||
#ifdef SQLITE_EBCDIC
|
||||
h += 9*(1&~(h>>4)); /* EBCDIC */
|
||||
#else
|
||||
h += 9*(1&(h>>6)); /* ASCII */
|
||||
#endif
|
||||
return h & 0xf;
|
||||
}
|
||||
|
||||
/*
|
||||
** The first character of buffer zIn[0..nIn-1] is a '['. This routine
|
||||
** checked to see if the buffer holds "[NNNN]" or "[+NNNN]" and if it
|
||||
** does it makes corresponding changes to the *pK value and *pI value
|
||||
** and returns true. If the input buffer does not match the patterns,
|
||||
** no changes are made to either *pK or *pI and this routine returns false.
|
||||
*/
|
||||
static int isOffset(
|
||||
const unsigned char *zIn, /* Text input */
|
||||
int nIn, /* Bytes of input */
|
||||
unsigned int *pK, /* half-byte cursor to adjust */
|
||||
unsigned int *pI /* Input index to adjust */
|
||||
){
|
||||
int i;
|
||||
unsigned int k = 0;
|
||||
unsigned char c;
|
||||
for(i=1; i<nIn && (c = zIn[i])!=']'; i++){
|
||||
if( !isxdigit(c) ) return 0;
|
||||
k = k*16 + hexToInt(c);
|
||||
}
|
||||
if( i==nIn ) return 0;
|
||||
*pK = 2*k;
|
||||
*pI += i;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Decode the text starting at zIn into a binary database file.
|
||||
** The maximum length of zIn is nIn bytes. Compute the binary database
|
||||
** file contain in space obtained from sqlite3_malloc().
|
||||
**
|
||||
** Return the number of bytes of zIn consumed. Or return -1 if there
|
||||
** is an error. One potential error is that the recipe specifies a
|
||||
** database file larger than MX_FILE_SZ bytes.
|
||||
**
|
||||
** Abort on an OOM.
|
||||
*/
|
||||
static int decodeDatabase(
|
||||
const unsigned char *zIn, /* Input text to be decoded */
|
||||
int nIn, /* Bytes of input text */
|
||||
unsigned char **paDecode, /* OUT: decoded database file */
|
||||
int *pnDecode /* OUT: Size of decoded database */
|
||||
){
|
||||
unsigned char *a; /* Database under construction */
|
||||
int mx = 0; /* Current size of the database */
|
||||
sqlite3_uint64 nAlloc = 4096; /* Space allocated in a[] */
|
||||
unsigned int i; /* Next byte of zIn[] to read */
|
||||
unsigned int j; /* Temporary integer */
|
||||
unsigned int k; /* half-byte cursor index for output */
|
||||
unsigned int n; /* Number of bytes of input */
|
||||
unsigned char b = 0;
|
||||
if( nIn<4 ) return -1;
|
||||
n = (unsigned int)nIn;
|
||||
a = sqlite3_malloc( nAlloc );
|
||||
if( a==0 ){
|
||||
fprintf(stderr, "Out of memory!\n");
|
||||
exit(1);
|
||||
}
|
||||
memset(a, 0, nAlloc);
|
||||
for(i=k=0; i<n; i++){
|
||||
char c = zIn[i];
|
||||
if( isxdigit(c) ){
|
||||
k++;
|
||||
if( k & 1 ){
|
||||
b = hexToInt(c)*16;
|
||||
}else{
|
||||
b += hexToInt(c);
|
||||
j = k/2 - 1;
|
||||
if( j>=nAlloc ){
|
||||
sqlite3_uint64 newSize;
|
||||
if( nAlloc==MX_FILE_SZ || j>=MX_FILE_SZ ){
|
||||
if( eVerbosity ){
|
||||
fprintf(stderr, "Input database too big: max %d bytes\n",
|
||||
MX_FILE_SZ);
|
||||
}
|
||||
sqlite3_free(a);
|
||||
return -1;
|
||||
}
|
||||
newSize = nAlloc*2;
|
||||
if( newSize<=j ){
|
||||
newSize = (j+4096)&~4095;
|
||||
}
|
||||
if( newSize>MX_FILE_SZ ){
|
||||
if( j>=MX_FILE_SZ ){
|
||||
sqlite3_free(a);
|
||||
return -1;
|
||||
}
|
||||
newSize = MX_FILE_SZ;
|
||||
}
|
||||
a = sqlite3_realloc( a, newSize );
|
||||
if( a==0 ){
|
||||
fprintf(stderr, "Out of memory!\n");
|
||||
exit(1);
|
||||
}
|
||||
assert( newSize > nAlloc );
|
||||
memset(a+nAlloc, 0, newSize - nAlloc);
|
||||
nAlloc = newSize;
|
||||
}
|
||||
if( j>=(unsigned)mx ){
|
||||
mx = (j + 4095)&~4095;
|
||||
if( mx>MX_FILE_SZ ) mx = MX_FILE_SZ;
|
||||
}
|
||||
assert( j<nAlloc );
|
||||
a[j] = b;
|
||||
}
|
||||
}else if( zIn[i]=='[' && i<n-3 && isOffset(zIn+i, nIn-i, &k, &i) ){
|
||||
continue;
|
||||
}else if( zIn[i]=='\n' && i<n-4 && memcmp(zIn+i,"\n--\n",4)==0 ){
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
*pnDecode = mx;
|
||||
*paDecode = a;
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
** Progress handler callback.
|
||||
**
|
||||
** The argument is the cutoff-time after which all processing should
|
||||
** stop. So return non-zero if the cut-off time is exceeded.
|
||||
*/
|
||||
static int progress_handler(void *pClientData) {
|
||||
FuzzCtx *p = (FuzzCtx*)pClientData;
|
||||
sqlite3_int64 iNow = timeOfDay();
|
||||
int rc = iNow>=p->iCutoffTime;
|
||||
sqlite3_int64 iDiff = iNow - p->iLastCb;
|
||||
if( iDiff > p->mxInterval ) p->mxInterval = iDiff;
|
||||
p->nCb++;
|
||||
if( rc==0 && p->mxCb>0 && p->mxCb<=p->nCb ) rc = 1;
|
||||
if( rc && !p->timeoutHit && eVerbosity ){
|
||||
printf("Timeout on progress callback %d\n", p->nCb);
|
||||
fflush(stdout);
|
||||
p->timeoutHit = 1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Disallow debugging pragmas such as "PRAGMA vdbe_debug" and
|
||||
** "PRAGMA parser_trace" since they can dramatically increase the
|
||||
** amount of output without actually testing anything useful.
|
||||
**
|
||||
** Also block ATTACH and DETACH
|
||||
*/
|
||||
static int block_troublesome_sql(
|
||||
void *Notused,
|
||||
int eCode,
|
||||
const char *zArg1,
|
||||
const char *zArg2,
|
||||
const char *zArg3,
|
||||
const char *zArg4
|
||||
){
|
||||
(void)Notused;
|
||||
(void)zArg2;
|
||||
(void)zArg3;
|
||||
(void)zArg4;
|
||||
if( eCode==SQLITE_PRAGMA ){
|
||||
if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0
|
||||
|| sqlite3_stricmp("parser_trace", zArg1)==0
|
||||
|| sqlite3_stricmp("temp_store_directory", zArg1)==0
|
||||
){
|
||||
return SQLITE_DENY;
|
||||
}
|
||||
}else if( (eCode==SQLITE_ATTACH || eCode==SQLITE_DETACH)
|
||||
&& zArg1 && zArg1[0] ){
|
||||
return SQLITE_DENY;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Run the SQL text
|
||||
*/
|
||||
static int runDbSql(sqlite3 *db, const char *zSql){
|
||||
int rc;
|
||||
sqlite3_stmt *pStmt;
|
||||
while( isspace(zSql[0]) ) zSql++;
|
||||
if( zSql[0]==0 ) return SQLITE_OK;
|
||||
if( eVerbosity>=3 ){
|
||||
printf("RUNNING-SQL: [%s]\n", zSql);
|
||||
fflush(stdout);
|
||||
}
|
||||
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
|
||||
if( eVerbosity>=4 ){
|
||||
int j;
|
||||
for(j=0; j<sqlite3_column_count(pStmt); j++){
|
||||
if( j ) printf(",");
|
||||
switch( sqlite3_column_type(pStmt, j) ){
|
||||
case SQLITE_NULL: {
|
||||
printf("NULL");
|
||||
break;
|
||||
}
|
||||
case SQLITE_INTEGER:
|
||||
case SQLITE_FLOAT: {
|
||||
printf("%s", sqlite3_column_text(pStmt, j));
|
||||
break;
|
||||
}
|
||||
case SQLITE_BLOB: {
|
||||
int n = sqlite3_column_bytes(pStmt, j);
|
||||
int i;
|
||||
const unsigned char *a;
|
||||
a = (const unsigned char*)sqlite3_column_blob(pStmt, j);
|
||||
printf("x'");
|
||||
for(i=0; i<n; i++){
|
||||
printf("%02x", a[i]);
|
||||
}
|
||||
printf("'");
|
||||
break;
|
||||
}
|
||||
case SQLITE_TEXT: {
|
||||
int n = sqlite3_column_bytes(pStmt, j);
|
||||
int i;
|
||||
const unsigned char *a;
|
||||
a = (const unsigned char*)sqlite3_column_blob(pStmt, j);
|
||||
printf("'");
|
||||
for(i=0; i<n; i++){
|
||||
if( a[i]=='\'' ){
|
||||
printf("''");
|
||||
}else{
|
||||
putchar(a[i]);
|
||||
}
|
||||
}
|
||||
printf("'");
|
||||
break;
|
||||
}
|
||||
} /* End switch() */
|
||||
} /* End for() */
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
} /* End if( eVerbosity>=4 ) */
|
||||
} /* End while( SQLITE_ROW */
|
||||
if( rc!=SQLITE_DONE && eVerbosity>=3 ){
|
||||
printf("SQL-ERROR: (%d) %s\n", rc, sqlite3_errmsg(db));
|
||||
fflush(stdout);
|
||||
}
|
||||
}else if( eVerbosity>=3 ){
|
||||
printf("SQL-ERROR (%d): %s\n", rc, sqlite3_errmsg(db));
|
||||
fflush(stdout);
|
||||
} /* End if( SQLITE_OK ) */
|
||||
return sqlite3_finalize(pStmt);
|
||||
}
|
||||
|
||||
/* Invoke this routine to run a single test case */
|
||||
int runCombinedDbSqlInput(const uint8_t *aData, size_t nByte){
|
||||
int rc; /* SQLite API return value */
|
||||
int iSql; /* Index in aData[] of start of SQL */
|
||||
unsigned char *aDb = 0; /* Decoded database content */
|
||||
int nDb = 0; /* Size of the decoded database */
|
||||
int i; /* Loop counter */
|
||||
int j; /* Start of current SQL statement */
|
||||
char *zSql = 0; /* SQL text to run */
|
||||
int nSql; /* Bytes of SQL text */
|
||||
FuzzCtx cx; /* Fuzzing context */
|
||||
|
||||
if( nByte<10 ) return 0;
|
||||
if( sqlite3_initialize() ) return 0;
|
||||
if( sqlite3_memory_used()!=0 ){
|
||||
int nAlloc = 0;
|
||||
int nNotUsed = 0;
|
||||
sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, &nAlloc, &nNotUsed, 0);
|
||||
fprintf(stderr,"Memory leak in mutator: %lld bytes in %d allocations\n",
|
||||
sqlite3_memory_used(), nAlloc);
|
||||
exit(1);
|
||||
}
|
||||
memset(&cx, 0, sizeof(cx));
|
||||
iSql = decodeDatabase((unsigned char*)aData, (int)nByte, &aDb, &nDb);
|
||||
if( iSql<0 ) return 0;
|
||||
nSql = nByte - iSql;
|
||||
if( eVerbosity>=2 ){
|
||||
printf(
|
||||
"****** %d-byte input, %d-byte database, %d-byte script "
|
||||
"******\n", (int)nByte, nDb, nSql);
|
||||
fflush(stdout);
|
||||
}
|
||||
rc = sqlite3_open(0, &cx.db);
|
||||
if( rc ) return 1;
|
||||
if( bVdbeDebug ){
|
||||
sqlite3_exec(cx.db, "PRAGMA vdbe_debug=ON", 0, 0, 0);
|
||||
}
|
||||
|
||||
/* Invoke the progress handler frequently to check to see if we
|
||||
** are taking too long. The progress handler will return true
|
||||
** (which will block further processing) if more than iTimeout seconds have
|
||||
** elapsed since the start of the test.
|
||||
*/
|
||||
cx.iLastCb = timeOfDay();
|
||||
cx.iCutoffTime = cx.iLastCb + iTimeout; /* Now + iTimeout seconds */
|
||||
cx.mxCb = mxProgressCb;
|
||||
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
|
||||
sqlite3_progress_handler(cx.db, 10, progress_handler, (void*)&cx);
|
||||
#endif
|
||||
|
||||
/* Set a limit on the maximum size of a prepared statement, and the
|
||||
** maximum length of a string or blob */
|
||||
if( vdbeOpLimit>0 ){
|
||||
sqlite3_limit(cx.db, SQLITE_LIMIT_VDBE_OP, vdbeOpLimit);
|
||||
}
|
||||
if( lengthLimit>0 ){
|
||||
sqlite3_limit(cx.db, SQLITE_LIMIT_LENGTH, lengthLimit);
|
||||
}
|
||||
|
||||
if( nDb>=20 && aDb[18]==2 && aDb[19]==2 ){
|
||||
aDb[18] = aDb[19] = 1;
|
||||
}
|
||||
rc = sqlite3_deserialize(cx.db, "main", aDb, nDb, nDb,
|
||||
SQLITE_DESERIALIZE_RESIZEABLE |
|
||||
SQLITE_DESERIALIZE_FREEONCLOSE);
|
||||
if( rc ){
|
||||
fprintf(stderr, "sqlite3_deserialize() failed with %d\n", rc);
|
||||
goto testrun_finished;
|
||||
}
|
||||
if( maxDbSize>0 ){
|
||||
sqlite3_int64 x = maxDbSize;
|
||||
sqlite3_file_control(cx.db, "main", SQLITE_FCNTL_SIZE_LIMIT, &x);
|
||||
}
|
||||
|
||||
/* Block debug pragmas and ATTACH/DETACH. But wait until after
|
||||
** deserialize to do this because deserialize depends on ATTACH */
|
||||
sqlite3_set_authorizer(cx.db, block_troublesome_sql, 0);
|
||||
|
||||
/* Consistent PRNG seed */
|
||||
sqlite3_randomness(0,0);
|
||||
|
||||
zSql = sqlite3_malloc( nSql + 1 );
|
||||
if( zSql==0 ){
|
||||
fprintf(stderr, "Out of memory!\n");
|
||||
}else{
|
||||
memcpy(zSql, aData+iSql, nSql);
|
||||
zSql[nSql] = 0;
|
||||
for(i=j=0; zSql[i]; i++){
|
||||
if( zSql[i]==';' ){
|
||||
char cSaved = zSql[i+1];
|
||||
zSql[i+1] = 0;
|
||||
if( sqlite3_complete(zSql+j) ){
|
||||
rc = runDbSql(cx.db, zSql+j);
|
||||
j = i+1;
|
||||
}
|
||||
zSql[i+1] = cSaved;
|
||||
if( rc==SQLITE_INTERRUPT || progress_handler(&cx) ){
|
||||
goto testrun_finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( j<i ){
|
||||
runDbSql(cx.db, zSql+j);
|
||||
}
|
||||
}
|
||||
testrun_finished:
|
||||
sqlite3_free(zSql);
|
||||
rc = sqlite3_close(cx.db);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stdout, "sqlite3_close() returns %d\n", rc);
|
||||
}
|
||||
if( eVerbosity ){
|
||||
fprintf(stdout, "Peak memory usages: %f MB\n",
|
||||
sqlite3_memory_highwater(1) / 1000000.0);
|
||||
}
|
||||
if( sqlite3_memory_used()!=0 ){
|
||||
int nAlloc = 0;
|
||||
int nNotUsed = 0;
|
||||
sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, &nAlloc, &nNotUsed, 0);
|
||||
fprintf(stderr,"Memory leak: %lld bytes in %d allocations\n",
|
||||
sqlite3_memory_used(), nAlloc);
|
||||
exit(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** END of the dbsqlfuzz code
|
||||
***************************************************************************/
|
||||
|
||||
/* Look at a SQL text and try to determine if it begins with a database
|
||||
** description, such as would be found in a dbsqlfuzz test case. Return
|
||||
** true if this does appear to be a dbsqlfuzz test case and false otherwise.
|
||||
*/
|
||||
static int isDbSql(unsigned char *a, int n){
|
||||
if( n>4 && memcmp(a,"\n--\n",4)==0 ) return 1;
|
||||
while( n>0 && isspace(a[0]) ){ a++; n--; }
|
||||
if( n>8 && memcmp(a,"53514c69",8)==0 ) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Methods for the VHandle object
|
||||
*/
|
||||
static int inmemClose(sqlite3_file *pFile){
|
||||
@@ -951,6 +1393,7 @@ int main(int argc, char **argv){
|
||||
if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
|
||||
quietFlag = 1;
|
||||
verboseFlag = 0;
|
||||
eVerbosity = 0;
|
||||
}else
|
||||
if( strcmp(z,"rebuild")==0 ){
|
||||
rebuildFlag = 1;
|
||||
@@ -976,8 +1419,18 @@ int main(int argc, char **argv){
|
||||
if( strcmp(z,"verbose")==0 || strcmp(z,"v")==0 ){
|
||||
quietFlag = 0;
|
||||
verboseFlag++;
|
||||
eVerbosity++;
|
||||
if( verboseFlag>1 ) runFlags |= SQL_TRACE;
|
||||
}else
|
||||
if( strcmp(z,"version")==0 ){
|
||||
int ii;
|
||||
const char *z;
|
||||
printf("SQLite %s %s\n", sqlite3_libversion(), sqlite3_sourceid());
|
||||
for(ii=0; (z = sqlite3_compileoption_get(ii))!=0; ii++){
|
||||
printf("%s\n", z);
|
||||
}
|
||||
return 0;
|
||||
}else
|
||||
{
|
||||
fatalError("unknown option: %s", argv[i]);
|
||||
}
|
||||
@@ -1230,6 +1683,26 @@ int main(int argc, char **argv){
|
||||
*/
|
||||
if( !verboseFlag && !quietFlag ) printf("%s:", zDbName);
|
||||
for(pSql=g.pFirstSql; pSql; pSql=pSql->pNext){
|
||||
if( isDbSql(pSql->a, pSql->sz) ){
|
||||
sqlite3_snprintf(sizeof(g.zTestName), g.zTestName, "sqlid=%d",pSql->id);
|
||||
if( verboseFlag ){
|
||||
printf("%s\n", g.zTestName);
|
||||
fflush(stdout);
|
||||
}else if( !quietFlag ){
|
||||
static int prevAmt = -1;
|
||||
int idx = pSql->seq;
|
||||
int amt = idx*10/(g.nSql);
|
||||
if( amt!=prevAmt ){
|
||||
printf(" %d%%", amt*10);
|
||||
fflush(stdout);
|
||||
prevAmt = amt;
|
||||
}
|
||||
}
|
||||
runCombinedDbSqlInput(pSql->a, pSql->sz);
|
||||
nTest++;
|
||||
g.zTestName[0] = 0;
|
||||
continue;
|
||||
}
|
||||
for(pDb=g.pFirstDb; pDb; pDb=pDb->pNext){
|
||||
int openFlags;
|
||||
const char *zVfs = "inmem";
|
||||
|
||||
Reference in New Issue
Block a user