1
0
mirror of https://github.com/sqlite/sqlite.git synced 2026-10-30 01:43:10 +03:00

Change the order of fields in an internal structure to avoid the possibility of leaking heap address information when a corrupt database is queried.

FossilOrigin-Name: 3f9a0ba4f4dffac7a4b9de30e66d94498a1433d96e327cfff43134b8856f4ba1
This commit is contained in:
dan
2026-10-24 14:37:18 +00:00
parent 9a9446f3b0
commit 98fced6200
4 changed files with 150 additions and 24 deletions

View File

@@ -99,6 +99,18 @@ typedef struct PGroup PGroup;
** object is positioned immediately after the page buffer to serve as an
** overrun area, so that overreads are harmless.
**
** Under some circumstances, using a strategically corrupt db file, one
** or two varints may be read from past the end of the page buffer and
** returned to the user (as rowid values). For this reason, we ensure that
** the first few elements of PgHdr1 are not pointers. If they were, then
** a determined attacker might be able to read the value of the pointer,
** which can be used as part of a larger exploit. Furthermore, because the
** first two elements of PgHdr1 are isBulkLocal and isAnchor - u16 variables
** that always contain either 1 or 0 - the PgHdr1 structure always begins
** with 4 bytes with the high-bit cleared, and so code attempting to read
** varints can never progress past this point and read any more sensitive
** information further inside the structure.
**
** Variables isBulkLocal and isAnchor were once type "u8". That works,
** but causes a 2-byte gap in the structure for most architectures (since
** pointers must be either 4 or 8-byte aligned). As this structure is located
@@ -107,7 +119,10 @@ typedef struct PGroup PGroup;
** read part of this structure before the corruption is detected. This
** can cause a valgrind error if the uninitialized gap is accessed. Using u16
** ensures there is no such gap, and therefore no bytes of uninitialized
** memory in the structure.
** memory in the structure. Keeping the u16 type also lets isBulkLocal,
** isAnchor and iKey fill exactly the 8 bytes (2+2+4) that precede the
** 8-byte-aligned "page" member, so no uninitialized padding is exposed to
** an overread.
**
** The pLruNext and pLruPrev pointers form a double-linked circular list
** of all pages that are unpinned. The PGroup.lru element (which should be
@@ -115,10 +130,11 @@ typedef struct PGroup PGroup;
** beginning and the end of the list.
*/
struct PgHdr1 {
sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */
unsigned int iKey; /* Key value (page number) */
u16 isBulkLocal; /* This page from bulk local storage */
u16 isAnchor; /* This is the PGroup.lru element */
unsigned int iKey; /* Key value (page number) */
sqlite3_pcache_page page; /* Base class. pBuf & pExtra. Deliberately not the
** first member - see the note above. */
PgHdr1 *pNext; /* Next in hash table chain */
PCache1 *pCache; /* Cache that currently owns this page */
PgHdr1 *pLruNext; /* Next in circular LRU list of unpinned pages */
@@ -126,6 +142,18 @@ struct PgHdr1 {
/* NB: pLruPrev is only valid if pLruNext!=0 */
};
/*
** Given the sqlite3_pcache_page object handed out to the pcache.c layer,
** recover the PgHdr1 that contains it. PgHdr1.page is no longer required to
** be the first member of PgHdr1, so this goes through the "page" member
** rather than casting the structure pointer. The fetch methods produce the
** other direction directly with &pPage->page, only on paths where pPage is
** known to be non-NULL. PAGE_TO_PGHDR1() is likewise only ever given a
** non-NULL page that this module previously handed out.
*/
#define PAGE_TO_PGHDR1(pPg) \
((PgHdr1*)(((char*)(pPg)) - offsetof(PgHdr1,page)))
/*
** A page is pinned if it is not on the LRU list. To be "pinned" means
** that the page is in active use and must not be deallocated.
@@ -576,7 +604,7 @@ static void pcache1ResizeHash(PCache1 *p){
**
** The PGroup mutex must be held when this function is called.
*/
static PgHdr1 *pcache1PinPage(PgHdr1 *pPage){
static sqlite3_pcache_page *pcache1PinPage(PgHdr1 *pPage){
assert( pPage!=0 );
assert( PAGE_IS_UNPINNED(pPage) );
assert( pPage->pLruNext );
@@ -590,7 +618,7 @@ static PgHdr1 *pcache1PinPage(PgHdr1 *pPage){
assert( pPage->isAnchor==0 );
assert( pPage->pCache->pGroup->lru.isAnchor==1 );
pPage->pCache->nRecyclable--;
return pPage;
return &pPage->page;
}
@@ -873,7 +901,7 @@ static int pcache1Pagecount(sqlite3_pcache *p){
** usually not needed, and by avoiding the stack initialization required
** for these steps, the main pcache1Fetch() procedure can run faster.
*/
static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
static SQLITE_NOINLINE sqlite3_pcache_page *pcache1FetchStage2(
PCache1 *pCache,
unsigned int iKey,
int createFlag
@@ -939,8 +967,9 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
if( iKey>pCache->iMaxKey ){
pCache->iMaxKey = iKey;
}
return &pPage->page;
}
return pPage;
return 0;
}
/*
@@ -1002,7 +1031,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
** the common case where pGroup->mutex is NULL. The pcache1Fetch() wrapper
** invokes the appropriate routine.
*/
static PgHdr1 *pcache1FetchNoMutex(
static sqlite3_pcache_page *pcache1FetchNoMutex(
sqlite3_pcache *p,
unsigned int iKey,
int createFlag
@@ -1025,7 +1054,7 @@ static PgHdr1 *pcache1FetchNoMutex(
if( PAGE_IS_UNPINNED(pPage) ){
return pcache1PinPage(pPage);
}else{
return pPage;
return &pPage->page;
}
}else if( createFlag ){
/* Steps 3, 4, and 5 implemented by this subroutine */
@@ -1035,13 +1064,13 @@ static PgHdr1 *pcache1FetchNoMutex(
}
}
#if PCACHE1_MIGHT_USE_GROUP_MUTEX
static PgHdr1 *pcache1FetchWithMutex(
static sqlite3_pcache_page *pcache1FetchWithMutex(
sqlite3_pcache *p,
unsigned int iKey,
int createFlag
){
PCache1 *pCache = (PCache1 *)p;
PgHdr1 *pPage;
sqlite3_pcache_page *pPage;
pcache1EnterMutex(pCache->pGroup);
pPage = pcache1FetchNoMutex(p, iKey, createFlag);
@@ -1059,7 +1088,6 @@ static sqlite3_pcache_page *pcache1Fetch(
PCache1 *pCache = (PCache1 *)p;
#endif
assert( offsetof(PgHdr1,page)==0 );
assert( pCache->bPurgeable || createFlag!=1 );
assert( pCache->bPurgeable || pCache->nMin==0 );
assert( pCache->bPurgeable==0 || pCache->nMin==10 );
@@ -1067,11 +1095,11 @@ static sqlite3_pcache_page *pcache1Fetch(
assert( pCache->nHash>0 );
#if PCACHE1_MIGHT_USE_GROUP_MUTEX
if( pCache->pGroup->mutex ){
return (sqlite3_pcache_page*)pcache1FetchWithMutex(p, iKey, createFlag);
return pcache1FetchWithMutex(p, iKey, createFlag);
}else
#endif
{
return (sqlite3_pcache_page*)pcache1FetchNoMutex(p, iKey, createFlag);
return pcache1FetchNoMutex(p, iKey, createFlag);
}
}
@@ -1087,7 +1115,7 @@ static void pcache1Unpin(
int reuseUnlikely
){
PCache1 *pCache = (PCache1 *)p;
PgHdr1 *pPage = (PgHdr1 *)pPg;
PgHdr1 *pPage = PAGE_TO_PGHDR1(pPg);
PGroup *pGroup = pCache->pGroup;
assert( pPage->pCache==pCache );
@@ -1123,7 +1151,7 @@ static void pcache1Rekey(
unsigned int iNew
){
PCache1 *pCache = (PCache1 *)p;
PgHdr1 *pPage = (PgHdr1 *)pPg;
PgHdr1 *pPage = PAGE_TO_PGHDR1(pPg);
PgHdr1 **pp;
unsigned int hOld, hNew;
assert( pPage->iKey==iOld );
@@ -1132,7 +1160,7 @@ static void pcache1Rekey(
pcache1EnterMutex(pCache->pGroup);
assert( pcache1FetchNoMutex(p, iOld, 0)==pPage ); /* pPg really is iOld */
assert( pcache1FetchNoMutex(p, iOld, 0)==&pPage->page ); /* pPg really iOld */
assert( pCache->nHash>0 && (pCache->nHash & (pCache->nHash-1))==0 );
hOld = iOld & (pCache->nHash-1);
pp = &pCache->apHash[hOld];