From 7a521cfb795dbc38168769933fe36c20a1d889f8 Mon Sep 17 00:00:00 2001 From: drh Date: Wed, 25 Apr 2007 18:23:52 +0000 Subject: [PATCH] Fix segfaults that can occur if a malloc failure happens just before a built-in function calls sqlite3_value_text(). (CVS 3874) FossilOrigin-Name: 9cb0ed6ee9827bc6884a0195044d5b6ad0de698e --- manifest | 17 ++--- manifest.uuid | 2 +- src/date.c | 13 ++-- src/func.c | 64 +++++++++++-------- src/vdbeapi.c | 2 +- test/malloc8.test | 158 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 214 insertions(+), 42 deletions(-) create mode 100644 test/malloc8.test diff --git a/manifest b/manifest index e13ecea3f9..f72d2611fd 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Disable\stests\sin\smisc7\sthat\sdo\snot\swork\son\swindows\sdue\sto\slimitations\sof\nthe\swindows\sfile\ssystem.\s(CVS\s3873) -D 2007-04-25T15:42:26 +C Fix\ssegfaults\sthat\scan\soccur\sif\sa\smalloc\sfailure\shappens\sjust\sbefore\na\sbuilt-in\sfunction\scalls\ssqlite3_value_text().\s(CVS\s3874) +D 2007-04-25T18:23:53 F Makefile.in 8cab54f7c9f5af8f22fd97ddf1ecfd1e1860de62 F Makefile.linux-gcc 2d8574d1ba75f129aba2019f0b959db380a90935 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028 @@ -64,11 +64,11 @@ F src/btree.h 9b2cc0d113c0bc2d37d244b9a394d56948c9acbf F src/build.c 1880da163d9aa404016242b8b76d69907f682cd8 F src/callback.c 6414ed32d55859d0f65067aa5b88d2da27b3af9e F src/complete.c 7d1a44be8f37de125fcafd3d3a018690b3799675 -F src/date.c 74b76691bddf58b634f6bf4a77c8c58234268c6e +F src/date.c 94a6777df13d2aaacd19de080d9e8d3444364133 F src/delete.c 5c0d89b3ef7d48fe1f5124bfe8341f982747fe29 F src/experimental.c 1b2d1a6cd62ecc39610e97670332ca073c50792b F src/expr.c 2f0f9f89efe9170e5e6ca5d5e93a9d5896fff5ac -F src/func.c 007d957c057bb42b0d37aa6ad4be0e1c67a8871b +F src/func.c acb2c5055629ae3eebd71868af10fe425ef05f06 F src/hash.c 67b23e14f0257b69a3e8aa663e4eeadc1a2b6fd5 F src/hash.h 1b3f7e2609141fd571f62199fc38687d262e9564 F src/insert.c 413cc06990cb3c401e64e596776c1e43934f8841 @@ -128,7 +128,7 @@ F src/vacuum.c 8bd895d29e7074e78d4e80f948e35ddc9cf2beef F src/vdbe.c 814dab208a156250bc5e77f827f4e0c8ad734820 F src/vdbe.h 0025259af1939fb264a545816c69e4b5b8d52691 F src/vdbeInt.h 4b19fd8febad3fd14c4c97adaefc06754d323132 -F src/vdbeapi.c 1fca7ff056d03f131caa6b1296bb221da65ed7f4 +F src/vdbeapi.c 245263aa2d70d87b1201753cddc881996f219843 F src/vdbeaux.c ef59545f53f90394283f2fd003375d3ebbf0bd6e F src/vdbefifo.c 3ca8049c561d5d67cbcb94dc909ae9bb68c0bf8f F src/vdbemem.c 981a113405bd9b80aeb71fe246a2f01708e8a8f7 @@ -273,6 +273,7 @@ F test/malloc4.test 59cd02f71b363302a04c4e77b97c0a1572eaa210 F test/malloc5.test f228cb7101ae403327824d327a1f5651d83ef0f2 F test/malloc6.test 025ae0b78542e0ddd000d23f79d93e9be9ba0f15 F test/malloc7.test 1cf52834509eac7ebeb92105dacd4669f9ca9869 +F test/malloc8.test ede3231e1d9359b3c618357e49cb1c62267382e7 F test/manydb.test 8de36b8d33aab5ef295b11d9e95310aeded31af8 F test/memdb.test a67bda4ff90a38f2b19f6c7f95aa7289e051d893 F test/memleak.test d2d2a1ff7105d32dc3fdf691458cf6cba58c7217 @@ -461,7 +462,7 @@ F www/tclsqlite.tcl bb0d1357328a42b1993d78573e587c6dcbc964b9 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5 -P 16979f4525652bfd6c6e5306eafc883bef3880aa -R 28b0457dabf779eb2bc14720d6227390 +P 66646d6fda067e19240808aef65fafd8fa177cdd +R 48b7346bd8cf5e77d4009859f7c6c683 U drh -Z 15c285b6aa265216fe50f5053ca77b43 +Z fd08492b87e717e5d32a8be7d387293c diff --git a/manifest.uuid b/manifest.uuid index 102849e16b..1a153c3032 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -66646d6fda067e19240808aef65fafd8fa177cdd \ No newline at end of file +9cb0ed6ee9827bc6884a0195044d5b6ad0de698e \ No newline at end of file diff --git a/src/date.c b/src/date.c index 3625fe5c72..5ff4045890 100644 --- a/src/date.c +++ b/src/date.c @@ -16,7 +16,7 @@ ** sqlite3RegisterDateTimeFunctions() found at the bottom of the file. ** All other code has file scope. ** -** $Id: date.c,v 1.62 2007/04/06 02:32:34 drh Exp $ +** $Id: date.c,v 1.63 2007/04/25 18:23:53 drh Exp $ ** ** NOTES: ** @@ -655,12 +655,15 @@ static int parseModifier(const char *zMod, DateTime *p){ */ static int isDate(int argc, sqlite3_value **argv, DateTime *p){ int i; + const unsigned char *z; if( argc==0 ) return 1; - if( SQLITE_NULL==sqlite3_value_type(argv[0]) || - parseDateOrTime((char*)sqlite3_value_text(argv[0]), p) ) return 1; + if( (z = sqlite3_value_text(argv[0]))==0 || parseDateOrTime((char*)z, p) ){ + return 1; + } for(i=1; i @@ -102,6 +102,7 @@ static void lengthFunc( } case SQLITE_TEXT: { const unsigned char *z = sqlite3_value_text(argv[0]); + if( z==0 ) return; for(len=0; *z; z++){ if( (0xc0&*z)!=0x80 ) len++; } sqlite3_result_int(context, len); break; @@ -212,30 +213,38 @@ static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ ** Implementation of the upper() and lower() SQL functions. */ static void upperFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ - unsigned char *z; + char *z1; + const char *z2; int i; if( argc<1 || SQLITE_NULL==sqlite3_value_type(argv[0]) ) return; - z = sqliteMalloc(sqlite3_value_bytes(argv[0])+1); - if( z==0 ) return; - strcpy((char*)z, (char*)sqlite3_value_text(argv[0])); - for(i=0; z[i]; i++){ - z[i] = toupper(z[i]); + z2 = (char*)sqlite3_value_text(argv[0]); + if( z2 ){ + z1 = sqlite3_malloc(sqlite3_value_bytes(argv[0])+1); + if( z1 ){ + strcpy(z1, z2); + for(i=0; z1[i]; i++){ + z1[i] = toupper(z1[i]); + } + sqlite3_result_text(context, z1, -1, sqlite3_free); + } } - sqlite3_result_text(context, (char*)z, -1, SQLITE_TRANSIENT); - sqliteFree(z); } static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ - unsigned char *z; + char *z1; + const char *z2; int i; if( argc<1 || SQLITE_NULL==sqlite3_value_type(argv[0]) ) return; - z = sqliteMalloc(sqlite3_value_bytes(argv[0])+1); - if( z==0 ) return; - strcpy((char*)z, (char*)sqlite3_value_text(argv[0])); - for(i=0; z[i]; i++){ - z[i] = tolower(z[i]); + z2 = (char*)sqlite3_value_text(argv[0]); + if( z2 ){ + z1 = sqlite3_malloc(sqlite3_value_bytes(argv[0])+1); + if( z1 ){ + strcpy(z1, z2); + for(i=0; z1[i]; i++){ + z1[i] = tolower(z1[i]); + } + sqlite3_result_text(context, z1, -1, sqlite3_free); + } } - sqlite3_result_text(context, (char*)z, -1, SQLITE_TRANSIENT); - sqliteFree(z); } /* @@ -523,6 +532,7 @@ static void likeFunc( ** Otherwise, return an error. */ const unsigned char *zEsc = sqlite3_value_text(argv[2]); + if( zEsc==0 ) return; if( sqlite3utf8CharLen((char*)zEsc, -1)!=1 ){ sqlite3_result_error(context, "ESCAPE expression must be a single character", -1); @@ -625,6 +635,7 @@ static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ const unsigned char *zArg = sqlite3_value_text(argv[0]); char *z; + if( zArg==0 ) return; for(i=n=0; zArg[i]; i++){ if( zArg[i]=='\'' ) n++; } z = sqliteMalloc( i+n+3 ); if( z==0 ) return; @@ -692,16 +703,14 @@ static void replaceFunc( int i, j; /* Loop counters */ assert( argc==3 ); - if( sqlite3_value_type(argv[0])==SQLITE_NULL || - sqlite3_value_type(argv[1])==SQLITE_NULL || - sqlite3_value_type(argv[2])==SQLITE_NULL ){ - return; - } zStr = sqlite3_value_text(argv[0]); + if( zStr==0 ) return; nStr = sqlite3_value_bytes(argv[0]); zPattern = sqlite3_value_text(argv[1]); + if( zPattern==0 ) return; nPattern = sqlite3_value_bytes(argv[1]); zRep = sqlite3_value_text(argv[2]); + if( zRep==0 ) return; nRep = sqlite3_value_bytes(argv[2]); if( nPattern>=nRep ){ nOut = nStr; @@ -746,14 +755,13 @@ static void trimFunc( return; } zIn = sqlite3_value_text(argv[0]); + if( zIn==0 ) return; nIn = sqlite3_value_bytes(argv[0]); if( argc==1 ){ static const unsigned char zSpace[] = " "; zCharSet = zSpace; - }else if( sqlite3_value_type(argv[1])==SQLITE_NULL ){ + }else if( (zCharSet = sqlite3_value_text(argv[1]))==0 ){ return; - }else{ - zCharSet = sqlite3_value_text(argv[1]); } cFirst = zCharSet[0]; if( cFirst ){ @@ -834,14 +842,16 @@ static void soundexFunc( */ static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){ const char *zFile = (const char *)sqlite3_value_text(argv[0]); - const char *zProc = 0; + const char *zProc; sqlite3 *db = sqlite3_user_data(context); char *zErrMsg = 0; if( argc==2 ){ zProc = (const char *)sqlite3_value_text(argv[1]); + }else{ + zProc = 0; } - if( sqlite3_load_extension(db, zFile, zProc, &zErrMsg) ){ + if( zFile && sqlite3_load_extension(db, zFile, zProc, &zErrMsg) ){ sqlite3_result_error(context, zErrMsg, -1); sqlite3_free(zErrMsg); } diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 3d3d815c7f..2e67cbdcb2 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -443,7 +443,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ Vdbe *pVm = (Vdbe *)pStmt; int vals = sqlite3_data_count(pStmt); if( i>=vals || i<0 ){ - static const Mem nullMem = {{0}, 0.0, "", 0, MEM_Null, MEM_Null }; + static const Mem nullMem = {{0}, 0.0, "", 0, MEM_Null, SQLITE_NULL }; sqlite3Error(pVm->db, SQLITE_RANGE, 0); return (Mem*)&nullMem; } diff --git a/test/malloc8.test b/test/malloc8.test new file mode 100644 index 0000000000..e624ca0946 --- /dev/null +++ b/test/malloc8.test @@ -0,0 +1,158 @@ +# 2006 July 26 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file contains additional out-of-memory checks (see malloc.tcl) +# added to expose a bug in out-of-memory handling for sqlite3_value_text() +# +# $Id: malloc8.test,v 1.1 2007/04/25 18:23:53 drh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Only run these tests if memory debugging is turned on. +# +if {[info command sqlite_malloc_stat]==""} { + puts "Skipping malloc tests: not compiled with -DSQLITE_MEMDEBUG..." + finish_test + return +} + +# Usage: do_malloc_test +# +# The first argument, , is an integer used to name the +# tests executed by this proc. Options are as follows: +# +# -tclprep TCL script to run to prepare test. +# -sqlprep SQL script to run to prepare test. +# -tclbody TCL script to run with malloc failure simulation. +# -sqlbody TCL script to run with malloc failure simulation. +# -cleanup TCL script to run after the test. +# +# This command runs a series of tests to verify SQLite's ability +# to handle an out-of-memory condition gracefully. It is assumed +# that if this condition occurs a malloc() call will return a +# NULL pointer. Linux, for example, doesn't do that by default. See +# the "BUGS" section of malloc(3). +# +# Each iteration of a loop, the TCL commands in any argument passed +# to the -tclbody switch, followed by the SQL commands in any argument +# passed to the -sqlbody switch are executed. Each iteration the +# Nth call to sqliteMalloc() is made to fail, where N is increased +# each time the loop runs starting from 1. When all commands execute +# successfully, the loop ends. +# +proc do_malloc_test {tn args} { + array unset ::mallocopts + array set ::mallocopts $args + + set ::go 1 + for {set ::n 1} {$::go && $::n < 50000} {incr ::n} { + do_test malloc8-$tn.$::n { + + sqlite_malloc_fail 0 + catch {db close} + sqlite3 db test.db + set ::DB [sqlite3_connection_pointer db] + + # Execute any -tclprep and -sqlprep scripts. + # + if {[info exists ::mallocopts(-tclprep)]} { + eval $::mallocopts(-tclprep) + } + if {[info exists ::mallocopts(-sqlprep)]} { + execsql $::mallocopts(-sqlprep) + } + + # Now set the ${::n}th malloc() to fail and execute the -tclbody and + # -sqlbody scripts. + # + sqlite_malloc_fail $::n + set ::mallocbody {} + if {[info exists ::mallocopts(-tclbody)]} { + append ::mallocbody "$::mallocopts(-tclbody)\n" + } + if {[info exists ::mallocopts(-sqlbody)]} { + append ::mallocbody "db eval {$::mallocopts(-sqlbody)}" + } + set v [catch $::mallocbody msg] + + # If the test fails (if $v!=0) and the database connection actually + # exists, make sure the failure code is SQLITE_NOMEM. + if {$v && [info command db]=="db" && [info exists ::mallocopts(-sqlbody)] + && [db errorcode]!=7} { + set v 999 + } + + set leftover [lindex [sqlite_malloc_stat] 2] + if {$leftover>0} { + if {$leftover>1} {puts "\nLeftover: $leftover\nReturn=$v Message=$msg"} + set ::go 0 + if {$v} { + puts "\nError message returned: $msg" + } else { + set v {1 1} + } + } else { + set v2 [expr {$msg=="" || $msg=="out of memory"}] + if {!$v2} {puts "\nError message returned: $msg"} + lappend v $v2 + } + } {1 1} + + if {[info exists ::mallocopts(-cleanup)]} { + catch [list uplevel #0 $::mallocopts(-cleanup)] msg + } + } + unset ::mallocopts +} + +# The setup is a database with UTF-16 encoding that contains a single +# large string. We will be running lots of queries against this +# database. Because we will be extracting the string as UTF-8, there +# is a type conversion that occurs and thus an opportunity for malloc() +# to fail and for sqlite3_value_text() to return 0 even though +# sqlite3_value_type() returns SQLITE_TEXT. +# +db close +file delete -force test.db test.db-journal +sqlite3 db test.db +db eval { + PRAGMA encoding='UTF-16'; + CREATE TABLE t1(a); + INSERT INTO t1 + VALUES('0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ'); +} + + +do_malloc_test 1 -sqlbody { + SELECT lower(a), upper(a), quote(a), trim(a), trim('x',a) FROM t1; +} +do_malloc_test 2 -sqlbody { + SELECT replace(a,'x','y'), replace('x',a,'y'), replace('x','y',a) + FROM t1; +} +do_malloc_test 3 -sqlbody { + SELECT length(a), substr(a, 4, 4) FROM t1; +} +do_malloc_test 4 -sqlbody { + SELECT julianday(a,a) FROM t1; +} +do_malloc_test 5 -sqlbody { + SELECT 1 FROM t1 WHERE a LIKE 'hello' ESCAPE NULL; +} + +# Ensure that no file descriptors were leaked. +do_test malloc-99.X { + catch {db close} + set sqlite_open_file_count +} {0} + +sqlite_malloc_fail 0 +finish_test