An exploitable use after free vulnerability exists in the window function functionality of Sqlite3 3.26.0. A specially crafted SQL command can cause a use after free vulnerability, potentially resulting in remote code execution. An attacker can send a malicious SQL command to trigger this vulnerability.
d390d551f82eb4acadb2846114226e2c7365ef75aec7f1a6558793c6fdbee529
Talos Vulnerability Report
TALOS-2019-0777
Sqlite3 Window Function Remote Code Execution Vulnerability
May 9, 2019
CVE Number
CVE-2019-5018
Summary
An exploitable use after free vulnerability exists in the window function functionality of Sqlite3 3.26.0. A specially crafted SQL command can cause a use after free vulnerability, potentially resulting in remote code execution. An attacker can send a malicious SQL command to trigger this vulnerability.
Tested Versions
SQLite 3.26.0, 3.27.0
Product URLs
https://sqlite.org/download.html
CVSSv3 Score
8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE
CWE-416: Use After Free
Details
SQLite is a popular library implementing a SQL database engine. It is used extensively in mobile devices, browsers, hardware devices, and user applications. It is a frequent choice for a small, fast, and reliable database solution.
SQLite implements the Window Functions feature of SQL which allows queries over a subset, or "window", of rows. After parsing a SELECT statement that contains a window function, the SELECT statement is transformed using the sqlite3WindowRewrite function.
src/select.c:5643
sqlite3SelectPrep(pParse, p, 0);
...
#ifndef SQLITE_OMIT_WINDOWFUNC
if( sqlite3WindowRewrite(pParse, p) ){
goto select_end;
}
During this function, the expression-list held by the SELECT object is rewritten if an aggregate function (COUNT, MAX, MIN, AVG, SUM) was used [0].
src/window.c:747
int sqlite3WindowRewrite(Parse *pParse, Select *p){
int rc = SQLITE_OK;
if( p->pWin && p->pPrior==0 ){
...
Window *pMWin = p->pWin; /* Master window object */
Window *pWin; /* Window object iterator */
...
selectWindowRewriteEList(pParse, pMWin /* window */, pSrc, p->pEList, &pSublist); [0]
selectWindowRewriteEList(pParse, pMWin /* window */, pSrc, p->pOrderBy, &pSublist);
...
pSublist = exprListAppendList(pParse, pSublist, pMWin->pPartition);
The master window object pMWin is taken from the SELECT object and is used during the rewrite [1]. This walks the expression list from the SELECT object and rewrites the window function(s) for easier processing.
src/window.c:692
static void selectWindowRewriteEList(
Parse *pParse,
Window *pWin,
SrcList *pSrc,
ExprList *pEList,
ExprList **ppSub
){
Walker sWalker;
WindowRewrite sRewrite;
memset(&sWalker, 0, sizeof(Walker));
memset(&sRewrite, 0, sizeof(WindowRewrite));
sRewrite.pSub = *ppSub;
sRewrite.pWin = pWin; // [1]
sRewrite.pSrc = pSrc;
sWalker.pParse = pParse;
sWalker.xExprCallback = selectWindowRewriteExprCb;
sWalker.xSelectCallback = selectWindowRewriteSelectCb;
sWalker.u.pRewrite = &sRewrite;
(void)sqlite3WalkExprList(&sWalker, pEList);
*ppSub = sRewrite.pSub;
}
Note the master window object is used in the WindowRewrite object. While processing each expression, the xExprCallback function is used as a callback for processing. When processing an aggregate function (TKAGGFUNCTION) and after appending to the expression list, the expression is deleted [2].
src/window.c:602
static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){
struct WindowRewrite *p = pWalker->u.pRewrite;
Parse *pParse = pWalker->pParse;
...
switch( pExpr->op ){
...
/* Fall through. */
case TK_AGG_FUNCTION:
case TK_COLUMN: {
Expr *pDup = sqlite3ExprDup(pParse->db, pExpr, 0);
p->pSub = sqlite3ExprListAppend(pParse, p->pSub, pDup);
if( p->pSub ){
assert( ExprHasProperty(pExpr, EP_Static)==0 );
ExprSetProperty(pExpr, EP_Static);
sqlite3ExprDelete(pParse->db, pExpr); [2]
ExprClearProperty(pExpr, EP_Static);
memset(pExpr, 0, sizeof(Expr));
pExpr->op = TK_COLUMN;
pExpr->iColumn = p->pSub->nExpr-1;
pExpr->iTable = p->pWin->iEphCsr;
}
...
}
During the deletion of the expression, if the expression is marked as a Window Function, the associated Window object is deleted as well.
src/window.c:1051
static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){
...
if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){
...
if( ExprHasProperty(p, EP_WinFunc) ){
assert( p->op==TK_FUNCTION );
sqlite3WindowDelete(db, p->y.pWin);
}
}
During the deletion of the Window, the assocated partition for the Window is deleted.
src/window.c:851
void sqlite3WindowDelete(sqlite3 *db, Window *p){
if( p ){
sqlite3ExprDelete(db, p->pFilter);
sqlite3ExprListDelete(db, p->pPartition);
sqlite3ExprListDelete(db, p->pOrderBy);
sqlite3ExprDelete(db, p->pEnd);
sqlite3ExprDelete(db, p->pStart);
sqlite3DbFree(db, p->zName);
sqlite3DbFree(db, p);
}
}
Looking back at the original sqlite3WindowRewrite function, this deleted partition is reused after the rewrite of the expression list [4].
src/window.c:785
selectWindowRewriteEList(pParse, pMWin, pSrc, p->pEList, &pSublist); [4]
selectWindowRewriteEList(pParse, pMWin, pSrc, p->pOrderBy, &pSublist);
pMWin->nBufferCol = (pSublist ? pSublist->nExpr : 0);
...
pSublist = exprListAppendList(pParse, pSublist, pMWin->pPartition); [5]
src/window.c:723
static ExprList *exprListAppendList(
Parse *pParse,
ExprList *pList,
ExprList *pAppend [5]
){
if( pAppend ){
int i;
int nInit = pList ? pList->nExpr : 0;
for(i=0; i<pAppend->nExpr; i++){
Expr *pDup = sqlite3ExprDup(pParse->db, pAppend->a[i].pExpr, 0);
pList = sqlite3ExprListAppend(pParse, pList, pDup);
if( pList ) pList->a[nInit+i].sortOrder = pAppend->a[i].sortOrder;
}
}
return pList;
}
After this partition is deleted, it is then reused in exprListAppendList [5], causing a use after free vulnerability, resulting in a denial of service. If an attacker can control this memory after the free, there is an opportunity to corrupt more data, potentially leading to code execution.
Crash Information
Using the debug version of sqlite3 to trash contents of freed buffer helps demonstrate this vulnerability [5]. Watching for a crash around 0xfafafafafafafafa would mean a freed buffer is being accessed again.
src/malloc.c:341
void sqlite3DbFreeNN(sqlite3 *db, void *p){
assert( db==0 || sqlite3_mutex_held(db->mutex) );
assert( p!=0 );
if( db ){
...
if( isLookaside(db, p) ){
LookasideSlot *pBuf = (LookasideSlot*)p;
/* Trash all content in the buffer being freed */
memset(p, 0xfa, db->lookaside.sz); [5]
pBuf->pNext = db->lookaside.pFree;
db->lookaside.pFree = pBuf;
return;
}
Running this slight modification through gdb sqlite3 with the proof of concept:
[─────────────────────REGISTERS──────────────────────]
*RAX 0xfafafafafafafafa
RBX 0x0
*RCX 0x7fffffd0
RDX 0x0
*RDI 0x7fffffffc3a0 —▸ 0x7ffff79c7340 (funlockfile) ◂— mov rdx, qword ptr [rdi + 0x88]
RSI 0x0
R8 0x0
*R9 0x30
R10 0x0
*R11 0x246
*R12 0x401a20 (_start) ◂— xor ebp, ebp
*R13 0x7fffffffe000 ◂— 0x2
R14 0x0
R15 0x0
*RBP 0x7fffffffc900 —▸ 0x7fffffffc990 —▸ 0x7fffffffcc10 —▸ 0x7fffffffce90 ◂— ...
*RSP 0x7fffffffc8d0 —▸ 0x4db4f5 (selectWindowRewriteSelectCb) ◂— push rbp
*RIP 0x4db723 (exprListAppendList+240) ◂— mov eax, dword ptr [rax]
[───────────────────────DISASM───────────────────────]
► 0x4db723 <exprListAppendList+240> mov eax, dword ptr [rax]
0x4db725 <exprListAppendList+242> cmp eax, dword ptr [rbp - 0x10]
0x4db728 <exprListAppendList+245> jg exprListAppendList+94 <0x4db691>
↓
0x4db691 <exprListAppendList+94> mov rax, qword ptr [rbp - 0x28]
0x4db695 <exprListAppendList+98> mov edx, dword ptr [rbp - 0x10]
0x4db698 <exprListAppendList+101> movsxd rdx, edx
0x4db69b <exprListAppendList+104> shl rdx, 5
0x4db69f <exprListAppendList+108> add rax, rdx
0x4db6a2 <exprListAppendList+111> add rax, 8
0x4db6a6 <exprListAppendList+115> mov rcx, qword ptr [rax]
0x4db6a9 <exprListAppendList+118> mov rax, qword ptr [rbp - 0x18]
[───────────────────────SOURCE───────────────────────]
145380 ){
145381 if( pAppend ){
145382 int i;
145383 int nInit = pList ? pList->nExpr : 0;
145384 printf("pAppend: [%p] -> %p\n", &pAppend, pAppend);
145385 for(i=0; i<pAppend->nExpr; i++){ // BUG-USE 0
145386 Expr *pDup = sqlite3ExprDup(pParse->db, pAppend->a[i].pExpr, 0);
145387 pList = sqlite3ExprListAppend(pParse, pList, pDup);
145388 if( pList ) pList->a[nInit+i].sortOrder = pAppend->a[i].sortOrder;
145389 }
[───────────────────────STACK────────────────────────]
00:0000│ rsp 0x7fffffffc8d0 —▸ 0x4db4f5 (selectWindowRewriteSelectCb) ◂— push rbp
01:0008│ 0x7fffffffc8d8 ◂— 0xfafafafafafafafa
02:0010│ 0x7fffffffc8e0 —▸ 0x746d58 ◂— 0x1
03:0018│ 0x7fffffffc8e8 —▸ 0x7fffffffdb30 —▸ 0x73b348 —▸ 0x736c60 (aVfs.13750) ◂— ...
04:0020│ 0x7fffffffc8f0 ◂— 0x100000000
05:0028│ 0x7fffffffc8f8 ◂— 0xce1ae95b8dd44700
06:0030│ rbp 0x7fffffffc900 —▸ 0x7fffffffc990 —▸ 0x7fffffffcc10 —▸ 0x7fffffffce90 ◂— ...
07:0038│ 0x7fffffffc908 —▸ 0x4db994 (sqlite3WindowRewrite+608) ◂— mov qword ptr [rbp - 0x68], rax
[─────────────────────BACKTRACE──────────────────────]
► f 0 4db723 exprListAppendList+240
f 1 4db994 sqlite3WindowRewrite+608
Exploit Proof of Concept
Run the proof of concept with the sqlite3 shell:
./sqlite3 -init poc
Timeline
2019-02-05 - Vendor Disclosure
2019-03-07 - 30 day follow up with vendor; awaiting moderator approval
2019-03-28 - Vendor patched
2019-05-09 - Public Release
Credit
Discovered by Cory Duplantis of Cisco Talos.