Skip to content

Commit c37b7ee

Browse files
authored
Fix out-of-bounds read in statement tail parser (#996)
* Fix out-of-bounds read in statement tail parser * Add comment with link to tail parsing issue * Fix missing increment in tail parser + tests
1 parent 2843e1f commit c37b7ee

File tree

4 files changed

+79
-51
lines changed

4 files changed

+79
-51
lines changed

src/better_sqlite3.cpp

+33-26
Original file line numberDiff line numberDiff line change
@@ -859,20 +859,27 @@ void Statement::JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info)
859859
if (handle == NULL) {
860860
return ThrowRangeError("The supplied SQL string contains no statements");
861861
}
862-
for (char c; (c = *tail); ++tail) {
863-
if (IS_SKIPPED(c)) continue;
862+
863+
for (char c; (c = *tail); ) {
864+
if (IS_SKIPPED(c)) {
865+
++tail;
866+
continue;
867+
}
864868
if (c == '/' && tail[1] == '*') {
865869
tail += 2;
866870
for (char c; (c = *tail); ++tail) {
867871
if (c == '*' && tail[1] == '/') {
868-
tail += 1;
872+
tail += 2;
869873
break;
870874
}
871875
}
872876
} else if (c == '-' && tail[1] == '-') {
873877
tail += 2;
874878
for (char c; (c = *tail); ++tail) {
875-
if (c == '\n') break;
879+
if (c == '\n') {
880+
++tail;
881+
break;
882+
}
876883
}
877884
} else {
878885
sqlite3_finalize(handle);
@@ -891,9 +898,9 @@ void Statement::JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info)
891898

892899
info.GetReturnValue().Set(info.This());
893900
}
894-
#line 149 "./src/objects/statement.lzz"
901+
#line 156 "./src/objects/statement.lzz"
895902
void Statement::JS_run (v8::FunctionCallbackInfo <v8 :: Value> const & info)
896-
#line 149 "./src/objects/statement.lzz"
903+
#line 156 "./src/objects/statement.lzz"
897904
{
898905
Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; ( ( void ) 0 ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; if ( ! db -> GetState ( ) -> unsafe_mode ) { if ( db -> GetState ( ) -> iterators ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; } ( ( void ) 0 ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; db -> GetState ( ) -> busy = true ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if ( db -> Log ( isolate , handle ) ) { db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; } ( ( void ) 0 ) ;
899906
sqlite3* db_handle = db->GetHandle();
@@ -916,9 +923,9 @@ void Statement::JS_run (v8::FunctionCallbackInfo <v8 :: Value> const & info)
916923
}
917924
db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ;
918925
}
919-
#line 172 "./src/objects/statement.lzz"
926+
#line 179 "./src/objects/statement.lzz"
920927
void Statement::JS_get (v8::FunctionCallbackInfo <v8 :: Value> const & info)
921-
#line 172 "./src/objects/statement.lzz"
928+
#line 179 "./src/objects/statement.lzz"
922929
{
923930
Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; if ( ! stmt -> returns_data ) return ThrowTypeError ( "This statement does not return data. Use run() instead" ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; db -> GetState ( ) -> busy = true ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if ( db -> Log ( isolate , handle ) ) { db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; } ( ( void ) 0 ) ;
924931
int status = sqlite3_step(handle);
@@ -933,9 +940,9 @@ void Statement::JS_get (v8::FunctionCallbackInfo <v8 :: Value> const & info)
933940
sqlite3_reset(handle);
934941
db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ;
935942
}
936-
#line 187 "./src/objects/statement.lzz"
943+
#line 194 "./src/objects/statement.lzz"
937944
void Statement::JS_all (v8::FunctionCallbackInfo <v8 :: Value> const & info)
938-
#line 187 "./src/objects/statement.lzz"
945+
#line 194 "./src/objects/statement.lzz"
939946
{
940947
Statement * stmt = node :: ObjectWrap :: Unwrap < Statement > ( info . This ( ) ) ; if ( ! stmt -> returns_data ) return ThrowTypeError ( "This statement does not return data. Use run() instead" ) ; sqlite3_stmt * handle = stmt -> handle ; Database * db = stmt -> db ; if ( ! db -> GetState ( ) -> open ) return ThrowTypeError ( "The database connection is not open" ) ; if ( db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ; if ( stmt -> locked ) return ThrowTypeError ( "This statement is busy executing a query" ) ; const bool bound = stmt -> bound ; if ( ! bound ) { Binder binder ( handle ) ; if ( ! binder . Bind ( info , info . Length ( ) , stmt ) ) { sqlite3_clear_bindings ( handle ) ; return ; } ( ( void ) 0 ) ; } else if ( info . Length ( ) > 0 ) { return ThrowTypeError ( "This statement already has bound parameters" ) ; } ( ( void ) 0 ) ; db -> GetState ( ) -> busy = true ; v8 :: Isolate * isolate = info . GetIsolate ( ) ; if ( db -> Log ( isolate , handle ) ) { db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ; } ( ( void ) 0 ) ;
941948
v8 :: Local < v8 :: Context > ctx = isolate -> GetCurrentContext ( ) ;
@@ -956,9 +963,9 @@ void Statement::JS_all (v8::FunctionCallbackInfo <v8 :: Value> const & info)
956963
if (js_error) db->GetState()->was_js_error = true;
957964
db -> GetState ( ) -> busy = false ; db -> ThrowDatabaseError ( ) ; if ( ! bound ) { sqlite3_clear_bindings ( handle ) ; } return ;
958965
}
959-
#line 208 "./src/objects/statement.lzz"
966+
#line 215 "./src/objects/statement.lzz"
960967
void Statement::JS_iterate (v8::FunctionCallbackInfo <v8 :: Value> const & info)
961-
#line 208 "./src/objects/statement.lzz"
968+
#line 215 "./src/objects/statement.lzz"
962969
{
963970
Addon * addon = static_cast < Addon * > ( info . Data ( ) . As < v8 :: External > ( ) -> Value ( ) ) ;
964971
v8 :: Isolate * isolate = info . GetIsolate ( ) ;
@@ -968,9 +975,9 @@ void Statement::JS_iterate (v8::FunctionCallbackInfo <v8 :: Value> const & info)
968975
addon->privileged_info = NULL;
969976
if (!maybeIterator.IsEmpty()) info.GetReturnValue().Set(maybeIterator.ToLocalChecked());
970977
}
971-
#line 218 "./src/objects/statement.lzz"
978+
#line 225 "./src/objects/statement.lzz"
972979
void Statement::JS_bind (v8::FunctionCallbackInfo <v8 :: Value> const & info)
973-
#line 218 "./src/objects/statement.lzz"
980+
#line 225 "./src/objects/statement.lzz"
974981
{
975982
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
976983
if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object");
@@ -981,9 +988,9 @@ void Statement::JS_bind (v8::FunctionCallbackInfo <v8 :: Value> const & info)
981988
stmt->bound = true;
982989
info.GetReturnValue().Set(info.This());
983990
}
984-
#line 229 "./src/objects/statement.lzz"
991+
#line 236 "./src/objects/statement.lzz"
985992
void Statement::JS_pluck (v8::FunctionCallbackInfo <v8 :: Value> const & info)
986-
#line 229 "./src/objects/statement.lzz"
993+
#line 236 "./src/objects/statement.lzz"
987994
{
988995
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
989996
if (!stmt->returns_data) return ThrowTypeError("The pluck() method is only for statements that return data");
@@ -994,9 +1001,9 @@ void Statement::JS_pluck (v8::FunctionCallbackInfo <v8 :: Value> const & info)
9941001
stmt->mode = use ? Data::PLUCK : stmt->mode == Data::PLUCK ? Data::FLAT : stmt->mode;
9951002
info.GetReturnValue().Set(info.This());
9961003
}
997-
#line 240 "./src/objects/statement.lzz"
1004+
#line 247 "./src/objects/statement.lzz"
9981005
void Statement::JS_expand (v8::FunctionCallbackInfo <v8 :: Value> const & info)
999-
#line 240 "./src/objects/statement.lzz"
1006+
#line 247 "./src/objects/statement.lzz"
10001007
{
10011008
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
10021009
if (!stmt->returns_data) return ThrowTypeError("The expand() method is only for statements that return data");
@@ -1007,9 +1014,9 @@ void Statement::JS_expand (v8::FunctionCallbackInfo <v8 :: Value> const & info)
10071014
stmt->mode = use ? Data::EXPAND : stmt->mode == Data::EXPAND ? Data::FLAT : stmt->mode;
10081015
info.GetReturnValue().Set(info.This());
10091016
}
1010-
#line 251 "./src/objects/statement.lzz"
1017+
#line 258 "./src/objects/statement.lzz"
10111018
void Statement::JS_raw (v8::FunctionCallbackInfo <v8 :: Value> const & info)
1012-
#line 251 "./src/objects/statement.lzz"
1019+
#line 258 "./src/objects/statement.lzz"
10131020
{
10141021
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
10151022
if (!stmt->returns_data) return ThrowTypeError("The raw() method is only for statements that return data");
@@ -1020,9 +1027,9 @@ void Statement::JS_raw (v8::FunctionCallbackInfo <v8 :: Value> const & info)
10201027
stmt->mode = use ? Data::RAW : stmt->mode == Data::RAW ? Data::FLAT : stmt->mode;
10211028
info.GetReturnValue().Set(info.This());
10221029
}
1023-
#line 262 "./src/objects/statement.lzz"
1030+
#line 269 "./src/objects/statement.lzz"
10241031
void Statement::JS_safeIntegers (v8::FunctionCallbackInfo <v8 :: Value> const & info)
1025-
#line 262 "./src/objects/statement.lzz"
1032+
#line 269 "./src/objects/statement.lzz"
10261033
{
10271034
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
10281035
if ( stmt -> db -> GetState ( ) -> busy ) return ThrowTypeError ( "This database connection is busy executing a query" ) ;
@@ -1031,9 +1038,9 @@ void Statement::JS_safeIntegers (v8::FunctionCallbackInfo <v8 :: Value> const &
10311038
else { if ( info . Length ( ) <= ( 0 ) || ! info [ 0 ] -> IsBoolean ( ) ) return ThrowTypeError ( "Expected " "first" " argument to be " "a boolean" ) ; stmt -> safe_ints = ( info [ 0 ] . As < v8 :: Boolean > ( ) ) -> Value ( ) ; }
10321039
info.GetReturnValue().Set(info.This());
10331040
}
1034-
#line 271 "./src/objects/statement.lzz"
1041+
#line 278 "./src/objects/statement.lzz"
10351042
void Statement::JS_columns (v8::FunctionCallbackInfo <v8 :: Value> const & info)
1036-
#line 271 "./src/objects/statement.lzz"
1043+
#line 278 "./src/objects/statement.lzz"
10371044
{
10381045
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
10391046
if (!stmt->returns_data) return ThrowTypeError("The columns() method is only for statements that return data");
@@ -1076,9 +1083,9 @@ void Statement::JS_columns (v8::FunctionCallbackInfo <v8 :: Value> const & info)
10761083

10771084
info.GetReturnValue().Set(columns);
10781085
}
1079-
#line 314 "./src/objects/statement.lzz"
1086+
#line 321 "./src/objects/statement.lzz"
10801087
void Statement::JS_busy (v8::Local <v8 :: String> _, v8::PropertyCallbackInfo <v8 :: Value> const & info)
1081-
#line 314 "./src/objects/statement.lzz"
1088+
#line 321 "./src/objects/statement.lzz"
10821089
{
10831090
Statement* stmt = node :: ObjectWrap :: Unwrap <Statement>(info.This());
10841091
info.GetReturnValue().Set(stmt->alive && stmt->locked);

src/better_sqlite3.hpp

+21-21
Original file line numberDiff line numberDiff line change
@@ -340,47 +340,47 @@ class Statement : public node::ObjectWrap
340340
explicit Statement (Database * db, sqlite3_stmt * handle, sqlite3_uint64 id, bool returns_data);
341341
#line 85 "./src/objects/statement.lzz"
342342
static void JS_new (v8::FunctionCallbackInfo <v8 :: Value> const & info);
343-
#line 149 "./src/objects/statement.lzz"
343+
#line 156 "./src/objects/statement.lzz"
344344
static void JS_run (v8::FunctionCallbackInfo <v8 :: Value> const & info);
345-
#line 172 "./src/objects/statement.lzz"
345+
#line 179 "./src/objects/statement.lzz"
346346
static void JS_get (v8::FunctionCallbackInfo <v8 :: Value> const & info);
347-
#line 187 "./src/objects/statement.lzz"
347+
#line 194 "./src/objects/statement.lzz"
348348
static void JS_all (v8::FunctionCallbackInfo <v8 :: Value> const & info);
349-
#line 208 "./src/objects/statement.lzz"
349+
#line 215 "./src/objects/statement.lzz"
350350
static void JS_iterate (v8::FunctionCallbackInfo <v8 :: Value> const & info);
351-
#line 218 "./src/objects/statement.lzz"
351+
#line 225 "./src/objects/statement.lzz"
352352
static void JS_bind (v8::FunctionCallbackInfo <v8 :: Value> const & info);
353-
#line 229 "./src/objects/statement.lzz"
353+
#line 236 "./src/objects/statement.lzz"
354354
static void JS_pluck (v8::FunctionCallbackInfo <v8 :: Value> const & info);
355-
#line 240 "./src/objects/statement.lzz"
355+
#line 247 "./src/objects/statement.lzz"
356356
static void JS_expand (v8::FunctionCallbackInfo <v8 :: Value> const & info);
357-
#line 251 "./src/objects/statement.lzz"
357+
#line 258 "./src/objects/statement.lzz"
358358
static void JS_raw (v8::FunctionCallbackInfo <v8 :: Value> const & info);
359-
#line 262 "./src/objects/statement.lzz"
359+
#line 269 "./src/objects/statement.lzz"
360360
static void JS_safeIntegers (v8::FunctionCallbackInfo <v8 :: Value> const & info);
361-
#line 271 "./src/objects/statement.lzz"
361+
#line 278 "./src/objects/statement.lzz"
362362
static void JS_columns (v8::FunctionCallbackInfo <v8 :: Value> const & info);
363-
#line 314 "./src/objects/statement.lzz"
363+
#line 321 "./src/objects/statement.lzz"
364364
static void JS_busy (v8::Local <v8 :: String> _, v8::PropertyCallbackInfo <v8 :: Value> const & info);
365-
#line 319 "./src/objects/statement.lzz"
365+
#line 326 "./src/objects/statement.lzz"
366366
Database * const db;
367-
#line 320 "./src/objects/statement.lzz"
367+
#line 327 "./src/objects/statement.lzz"
368368
sqlite3_stmt * const handle;
369-
#line 321 "./src/objects/statement.lzz"
369+
#line 328 "./src/objects/statement.lzz"
370370
Extras * const extras;
371-
#line 322 "./src/objects/statement.lzz"
371+
#line 329 "./src/objects/statement.lzz"
372372
bool alive;
373-
#line 323 "./src/objects/statement.lzz"
373+
#line 330 "./src/objects/statement.lzz"
374374
bool locked;
375-
#line 324 "./src/objects/statement.lzz"
375+
#line 331 "./src/objects/statement.lzz"
376376
bool bound;
377-
#line 325 "./src/objects/statement.lzz"
377+
#line 332 "./src/objects/statement.lzz"
378378
bool has_bind_map;
379-
#line 326 "./src/objects/statement.lzz"
379+
#line 333 "./src/objects/statement.lzz"
380380
bool safe_ints;
381-
#line 327 "./src/objects/statement.lzz"
381+
#line 334 "./src/objects/statement.lzz"
382382
char mode;
383-
#line 328 "./src/objects/statement.lzz"
383+
#line 335 "./src/objects/statement.lzz"
384384
bool const returns_data;
385385
};
386386
#line 1 "./src/objects/statement-iterator.lzz"

src/objects/statement.lzz

+11-4
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,27 @@ private:
113113
if (handle == NULL) {
114114
return ThrowRangeError("The supplied SQL string contains no statements");
115115
}
116-
for (char c; (c = *tail); ++tail) {
117-
if (IS_SKIPPED(c)) continue;
116+
// https://github.com/WiseLibs/better-sqlite3/issues/975#issuecomment-1520934678
117+
for (char c; (c = *tail); ) {
118+
if (IS_SKIPPED(c)) {
119+
++tail;
120+
continue;
121+
}
118122
if (c == '/' && tail[1] == '*') {
119123
tail += 2;
120124
for (char c; (c = *tail); ++tail) {
121125
if (c == '*' && tail[1] == '/') {
122-
tail += 1;
126+
tail += 2;
123127
break;
124128
}
125129
}
126130
} else if (c == '-' && tail[1] == '-') {
127131
tail += 2;
128132
for (char c; (c = *tail); ++tail) {
129-
if (c == '\n') break;
133+
if (c == '\n') {
134+
++tail;
135+
break;
136+
}
130137
}
131138
} else {
132139
sqlite3_finalize(handle);

test/13.database.prepare.js

+14
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ describe('Database#prepare()', function () {
3737
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);CREATE TABLE animals (name TEXT)')).to.throw(RangeError);
3838
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);/')).to.throw(RangeError);
3939
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);-')).to.throw(RangeError);
40+
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);--\n/')).to.throw(RangeError);
41+
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);--\nSELECT 123')).to.throw(RangeError);
42+
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);-- comment\nSELECT 123')).to.throw(RangeError);
43+
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);/**/-')).to.throw(RangeError);
44+
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);/**/SELECT 123')).to.throw(RangeError);
45+
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);/* comment */SELECT 123')).to.throw(RangeError);
4046
});
4147
it('should create a prepared Statement object', function () {
4248
const stmt1 = this.db.prepare('CREATE TABLE people (name TEXT) ');
@@ -57,4 +63,12 @@ describe('Database#prepare()', function () {
5763
assertStmt(this.db.prepare('BEGIN EXCLUSIVE'), 'BEGIN EXCLUSIVE', this.db, false, false);
5864
assertStmt(this.db.prepare('DELETE FROM data RETURNING *'), 'DELETE FROM data RETURNING *', this.db, true, false);
5965
});
66+
it('should create a prepared Statement object ignoring trailing comments and whitespace', function () {
67+
assertStmt(this.db.prepare('SELECT 555; '), 'SELECT 555; ', this.db, true, true);
68+
assertStmt(this.db.prepare('SELECT 555;-- comment'), 'SELECT 555;-- comment', this.db, true, true);
69+
assertStmt(this.db.prepare('SELECT 555;--abc\n--de\n--f'), 'SELECT 555;--abc\n--de\n--f', this.db, true, true);
70+
assertStmt(this.db.prepare('SELECT 555;/* comment */'), 'SELECT 555;/* comment */', this.db, true, true);
71+
assertStmt(this.db.prepare('SELECT 555;/* comment */-- comment'), 'SELECT 555;/* comment */-- comment', this.db, true, true);
72+
assertStmt(this.db.prepare('SELECT 555;-- comment\n/* comment */'), 'SELECT 555;-- comment\n/* comment */', this.db, true, true);
73+
});
6074
});

0 commit comments

Comments
 (0)