8.1 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
HIGH
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
6.8 Medium
CVSS2
Access Vector
NETWORK
Access Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:M/Au:N/C:P/I:P/A:P
0.112 Low
EPSS
Percentile
95.1%
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.
SQLite 3.26.0, 3.27.0
<https://sqlite.org/download.html>
8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-416: Use After Free
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 (TK_AGG_FUNCTION) 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.
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
Run the proof of concept with the sqlite3
shell:
./sqlite3 -init poc
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
8.1 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
HIGH
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
6.8 Medium
CVSS2
Access Vector
NETWORK
Access Complexity
MEDIUM
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:M/Au:N/C:P/I:P/A:P
0.112 Low
EPSS
Percentile
95.1%