Protect against possible memory corruption (src/backend/access/nbtree/nbtxlog.c)
Hi,
While analyzing a possible use of an uninitialized variable, I checked that
*_bt_restore_page* can lead to memory corruption,
by not checking the maximum limit of array items which is
MaxIndexTuplesPerPage.
It can also generate a dangling pointer by incrementing it beyond the
limits it can point to.
While there, I promoted a reduction of scope and adaptation of the type of
the *len* parameter to match XLogRecGetBlockData function.
pass regress check at Windows and check-world at Linux.
regards,
Ranier Vilela
Attachments:
0001-_bt_restore_page-have-issues-can-lead-a-memory-corru.patchapplication/octet-stream; name=0001-_bt_restore_page-have-issues-can-lead-a-memory-corru.patchDownload
From f965132abc3520f06dbfc8d5f89222cf00728e00 Mon Sep 17 00:00:00 2001
From: Ranier Vilela <ranier.vf@gmail.com>
Date: Sun, 11 Jul 2021 16:17:50 -0300
Subject: [PATCH] _bt_restore_page have issues can lead a memory corrupt, when
recovery tries restore index tuples to a page.
First the maximum index tuples per page varies that system to system.
at Windows 64 bits, the MaxIndexTuplesPerPage is 408.
The int *len* parameter allows 268.435.455 index tuples per page.
2.147.483.647 / sizeof(IndexTupleData) = 268.435.455 /* IndexTupleData size is 8 bytes */
So if _bt_restore_page tries restore a file corrupted or maliciously created,
can write after array bounds.
Fix by checking and raising a error PANIC in this case.
Another issue is that _bt_restore_page can increment the pointer *from*,
to after bounds that can point.
Fix by removing the unnecessary increment.
While there, reduce scope of two variables.
Author: Ranier Vilela (ranier.vf@gmail.com)
---
src/backend/access/nbtree/nbtxlog.c | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
index c2e920f159..8fe80bc8ff 100644
--- a/src/backend/access/nbtree/nbtxlog.c
+++ b/src/backend/access/nbtree/nbtxlog.c
@@ -35,16 +35,19 @@ static MemoryContext opCtx; /* working memory for operations */
* the one with highest item number appears first (lowest on the page).
*/
static void
-_bt_restore_page(Page page, char *from, int len)
+_bt_restore_page(Page page, char *from, Size len)
{
- IndexTupleData itupdata;
- Size itemsz;
- char *end = from + len;
+ const char *end = from + len;
Item items[MaxIndexTuplesPerPage];
uint16 itemsizes[MaxIndexTuplesPerPage];
int i;
int nitems;
+ /* Protect against corrupted recovery file */
+ nitems = (len / sizeof(IndexTupleData));
+ if (nitems < 0 || nitems > MaxIndexTuplesPerPage)
+ elog(PANIC, "_bt_restore_page: cannot restore %d items to page", nitems);
+
/*
* To get the items back in the original order, we add them to the page in
* reverse. To figure out where one tuple ends and another begins, we
@@ -53,6 +56,9 @@ _bt_restore_page(Page page, char *from, int len)
i = 0;
while (from < end)
{
+ IndexTupleData itupdata;
+ Size itemsz;
+
/*
* As we step through the items, 'from' won't always be properly
* aligned, so we need to use memcpy(). Further, we use Item (which
@@ -73,12 +79,9 @@ _bt_restore_page(Page page, char *from, int len)
nitems = i;
for (i = nitems - 1; i >= 0; i--)
- {
if (PageAddItem(page, items[i], itemsizes[i], nitems - i,
false, false) == InvalidOffsetNumber)
elog(PANIC, "_bt_restore_page: cannot add item to page");
- from += itemsz;
- }
}
static void
--
2.32.0.windows.1
On 11/07/2021 22:51, Ranier Vilela wrote:
Hi,
While analyzing a possible use of an uninitialized variable, I checked that
*_bt_restore_page* can lead to memory corruption,
by not checking the maximum limit of array items which is
MaxIndexTuplesPerPage.
+ /* Protect against corrupted recovery file */ + nitems = (len / sizeof(IndexTupleData)); + if (nitems < 0 || nitems > MaxIndexTuplesPerPage) + elog(PANIC, "_bt_restore_page: cannot restore %d items to page", nitems); +
That's not right. You don't get the number of items by dividing like
that. 'len' includes the tuple data as well, not just the IndexTupleData
header.
@@ -73,12 +79,9 @@ _bt_restore_page(Page page, char *from, int len)
nitems = i;for (i = nitems - 1; i >= 0; i--)
- {
if (PageAddItem(page, items[i], itemsizes[i], nitems - i,
false, false) == InvalidOffsetNumber)
elog(PANIC, "_bt_restore_page: cannot add item to page");
- from += itemsz;
- }
}
I agree with this change (except that I would leave the braces in
place). The 'from' that's calculated here is plain wrong; oversight in
commit 7e30c186da. Fortunately it's not used, so it can just be removed.
- Heikki
Em dom., 11 de jul. de 2021 às 19:19, Heikki Linnakangas <hlinnaka@iki.fi>
escreveu:
On 11/07/2021 22:51, Ranier Vilela wrote:
Hi,
While analyzing a possible use of an uninitialized variable, I checked
that
*_bt_restore_page* can lead to memory corruption,
by not checking the maximum limit of array items which is
MaxIndexTuplesPerPage.+ /* Protect against corrupted recovery file */ + nitems = (len / sizeof(IndexTupleData)); + if (nitems < 0 || nitems > MaxIndexTuplesPerPage) + elog(PANIC, "_bt_restore_page: cannot restore %d items topage", nitems);
+
That's not right. You don't get the number of items by dividing like
that. 'len' includes the tuple data as well, not just the IndexTupleData
header.
Thanks for the quick review.
Not totally wrong.
If it is not possible, know the upper limits, before the loop.
It is necessary to do this inside the loop.
attached v1 of patch.
regards,
Ranier Vilela
Attachments:
v1-0001-_bt_restore_page-have-issues-can-lead-a-memory-co.patchapplication/octet-stream; name=v1-0001-_bt_restore_page-have-issues-can-lead-a-memory-co.patchDownload
From 09634c2b3f63e5afb12d5d65c39d063098873cd2 Mon Sep 17 00:00:00 2001
From: Ranier Vilela <ranier.vf@gmail.com>
Date: Sun, 11 Jul 2021 20:25:59 -0300
Subject: [PATCH v1] _bt_restore_page have issues can lead a memory corrupt.
When recovery tries restore index tuples to a page.
The maximum index tuples per page varies that system to system.
at Windows 64 bits, the MaxIndexTuplesPerPage is 408.
The int *len* parameter allows an unknown number of index tuples,
that can write go beyond array boundaries.
So if _bt_restore_page tries restore a file corrupted or maliciously created,
can write after array bounds.
Fix by checking the upper limit.
Another issue is that _bt_restore_page can increment the pointer *from*,
to after bounds that can point.
Fix by removing the unnecessary increment.
While there, reduce scope of two variables.
Author: Ranier Vilela (ranier.vf@gmail.com)
---
src/backend/access/nbtree/nbtxlog.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
index c2e920f159..27282e9c8f 100644
--- a/src/backend/access/nbtree/nbtxlog.c
+++ b/src/backend/access/nbtree/nbtxlog.c
@@ -35,11 +35,9 @@ static MemoryContext opCtx; /* working memory for operations */
* the one with highest item number appears first (lowest on the page).
*/
static void
-_bt_restore_page(Page page, char *from, int len)
+_bt_restore_page(Page page, char *from, Size len)
{
- IndexTupleData itupdata;
- Size itemsz;
- char *end = from + len;
+ const char *end = from + len;
Item items[MaxIndexTuplesPerPage];
uint16 itemsizes[MaxIndexTuplesPerPage];
int i;
@@ -49,10 +47,14 @@ _bt_restore_page(Page page, char *from, int len)
* To get the items back in the original order, we add them to the page in
* reverse. To figure out where one tuple ends and another begins, we
* have to scan them in forward order first.
+ * Check the array upper limit to not overtake him.
*/
i = 0;
- while (from < end)
+ while (from < end && i <= MaxIndexTuplesPerPage)
{
+ IndexTupleData itupdata;
+ Size itemsz;
+
/*
* As we step through the items, 'from' won't always be properly
* aligned, so we need to use memcpy(). Further, we use Item (which
@@ -77,7 +79,6 @@ _bt_restore_page(Page page, char *from, int len)
if (PageAddItem(page, items[i], itemsizes[i], nitems - i,
false, false) == InvalidOffsetNumber)
elog(PANIC, "_bt_restore_page: cannot add item to page");
- from += itemsz;
}
}
--
2.32.0.windows.1
On 12/07/2021 02:34, Ranier Vilela wrote:
If it is not possible, know the upper limits, before the loop.
It is necessary to do this inside the loop.
@@ -49,10 +47,14 @@ _bt_restore_page(Page page, char *from, int len) * To get the items back in the original order, we add them to the page in * reverse. To figure out where one tuple ends and another begins, we * have to scan them in forward order first. + * Check the array upper limit to not overtake him. */ i = 0; - while (from < end) + while (from < end && i <= MaxIndexTuplesPerPage) { + IndexTupleData itupdata; + Size itemsz; + /* * As we step through the items, 'from' won't always be properly * aligned, so we need to use memcpy(). Further, we use Item (which
If we bother checking it, we should throw an error if the check fails,
not just silently soldier on. Also, shouldn't it be '<', not '<='? In
general though, we don't do much checking on WAL records, we assume that
the contents are sane. It would be nice to add more checks and make WAL
redo routines more robust to corrupt records, but this seems like an odd
place to start.
I committed the removal of bogus assignment to 'from'. Thanks!
- Heikki
Em seg., 12 de jul. de 2021 às 05:20, Heikki Linnakangas <hlinnaka@iki.fi>
escreveu:
On 12/07/2021 02:34, Ranier Vilela wrote:
If it is not possible, know the upper limits, before the loop.
It is necessary to do this inside the loop.@@ -49,10 +47,14 @@ _bt_restore_page(Page page, char *from, int len)
* To get the items back in the original order, we add them to thepage in
* reverse. To figure out where one tuple ends and another
begins, we
* have to scan them in forward order first. + * Check the array upper limit to not overtake him. */ i = 0; - while (from < end) + while (from < end && i <= MaxIndexTuplesPerPage) { + IndexTupleData itupdata; + Size itemsz; + /* * As we step through the items, 'from' won't always beproperly
* aligned, so we need to use memcpy(). Further, we use
Item (which
If we bother checking it, we should throw an error if the check fails,
not just silently soldier on. Also, shouldn't it be '<', not '<='?
Should be '<', you are right.
In
general though, we don't do much checking on WAL records, we assume that
the contents are sane. It would be nice to add more checks and make WAL
redo routines more robust to corrupt records, but this seems like an odd
place to start.
If WAL records can't be corrupted at _bt_restore_page, that's ok, it's safe.
I committed the removal of bogus assignment to 'from'. Thanks!
Thanks for the commit.
regards,
Ranier Vilela