Talos Vulnerability Report


Sqlite3 Window Function Remote Code Execution Vulnerability

May 9, 2019
CVE Number



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


CVSSv3 Score

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.

sqlite3SelectPrep(pParse, p, 0);
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].

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.

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].

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.

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.

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].

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]

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.

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;

Running this slight modification through gdb sqlite3 with the proof of concept:

*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]
► 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]
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      }
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
► 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


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


Discovered by Cory Duplantis of Cisco Talos.