Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux - Parity release harden Page Cache #1553

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

gcmoreira
Copy link
Contributor

In the context of #1516 and related issues, this PR addresses several bugs in the Page Cache API code and its associated functions.

NOTE1: This requires the fixes from #1543 (linked list object extensions) and #1545 (mountinfo). Please DO NOT test the samples below using only the changes from this branch!

NOTE2: From now on, the linux.pagecache.InodePages plugin will no longer display inode page details when using the --dump argument. This change helps prevent redundant scanning of the inode page cache, significantly improving processing time, especially when dealing with large files, such as those mentioned in the related issues.

fix: #1526 , #1527

Issue 1526

Sample: broken_rhel_load_as_2.lime

$ time ./vol.py -f ./broken_rhel_load_as_2.lime linux.pagecache.Files 2>/tmp/broken_rhel_load_as_2.err | wc -l
23920

real    1m19.729s
...
$ wc -l /tmp/broken_rhel_load_as_2.err 
0 /tmp/broken_rhel_load_as_2.err

Sample: Ubuntu_Server_x64_22.04_Auth.zip.lime

$ time ./vol.py -f ./Ubuntu_Server_x64_22.04_Auth.lime linux.pagecache.Files 2>/tmp/Ubuntu_Server_x64_22.04_Auth.err | wc -l
82978

real    4m31.273s

$ wc -l /tmp/Ubuntu_Server_x64_22.04_Auth.err
0 /tmp/Ubuntu_Server_x64_22.04_Auth.err

Issue 1527

1 - Page out of the bounds issue

Fixes the issue reported by @atcuno in #1527 related to handling page out-of-bounds scenarios.

2 - Sample: unshared_fds_network_connection.zip.lime

I'm not sure how the /proc/kcore file ended up being written in that case. It's likely due to all these recent changes, however, in my tests the inode for the /proc/kcore file in that sample shows zero cached pages, meaning that using --dump doesn't result in any file being written.

$ ./vol.py -f ../unshared_fds_network_connection.lime linux.pagecache.Files | grep 9ec482a94e30
SuperblockAddr  MountPoint      Device  InodeNum        InodeAddr       FileType        InodePages      CachedPages     FileMode        AccessTime      ModificationTime      ChangeTime       FilePath
...
0x9ec482298000  /proc   0:21    4026532026      0x9ec482a94e30  REG     34359734275     0       -r--------      2024-08-20 18:57:13.620000 UTC  2024-08-20 18:57:13.620000 UTC2024-08-20 18:57:13.620000 UTC   /proc/kcore

Regarding the dump size limit: I can add an argument to restrict the file size in a future PR. However, this shouldn't be an issue in most cases since the output is a sparse file, which is supported by most modern filesystems across all major operating systems.

  • Linux: Ext2/3/4, XFS, JFS, ReiserFS, Btrfs, ZFS and even NTFS-3G
  • Windows: NTFS, exFAT and ReFS.
  • MAC: APFS, HFS+ and exFAT

The actual file size (not the apparent / sparsed size reported by the OS) will only include the pages found in memory.
Even if we dump all existing inodes from the entire Page Cache, the total actual occupied size, by definition, cannot exceed the system RAM size. Even in the hypothetical (and absurd) scenario where 100% of RAM is used by the Page Cache, the resulting sizes are still too small to break a filesystem.
Naturally, if you have just 100MB of available space and the actual written size is 16GB, you'll run into issues.
However, this can happen with any command in the operating system, i.e.: cp /some/big/file /some/small/fs/.
Curiously, cp does not have an option to limit the size of the data being written.

Additionally, ls will report only the apparent/sparsed size, not the actual size. You can even have a sparse file larger than the space available on disk. For instance:

$ df -h .
Filesystem               Size  Used Avail Use% Mounted on
/dev/nvme1n1p1           469G   93G  353G  21% /mnt/other_disk

There's 353GB available, let's create a 512GB sparse file:

$ truncate -s 512G file.img
$ ls -la file.img 
-rw-rw-r-- 1 gmoreira gmoreira 549755813888 Jan 14 19:46 file.img

Is the filesystem full or broken?

$ df -h
Filesystem               Size  Used Avail Use% Mounted on
/dev/nvme1n1p1           469G   93G  353G  21% /mnt/other_disk

It doesn't take up any space. To read the actual file size, you can use du.

$ du -hs file.img 
0 file.img

On the other hand, files like /proc/kcore will never report pages available in the Page Cache since it's not a normal file backed on disk blocks but a special file in the proc filesystem. It directly represents in-memory content, so caching is unnecessary and irrelevant to its purpose.

Having said that, I've added lazy initialization for the output sparse file. This ensures that if the linux.pagecache.InodePages plugin cannot to find any valid pages to dump, it will still generate the file, however, in such cases, the file will be created but remain empty with 0 actual bytes and not truncated to the inode size.

3 - Sample: 20220822195350.zip

Effectively, the file contains 55007 pages which is approx ~215MB.

$ ./vol.py -f ../20220822195350_data.lime linux.pagecache.Files | grep 888223baee58
0x88822de2b000.0/       259:1   4822135g0x888223baee58  REG     55006   55007   -rw-r--r--      2022-08-16 20:31:07.000000 UTC  2022-08-16 20:31:07.000000 UTC  2022-08-22 19:06:06.114498 UTC /var/cache/yum/x86_64/2/amzn2-core/gen/primary_db.sqlite

It's odd it reports more pages available than the actual file size. I've already debugged and verified this, see my notes later.

$ time ./vol.py -f ./20220822195350_data.lime linux.pagecache.Inode --inode 0x888223baee58 --dump 
...
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x888223baee58, inode size 225304576, page index 55006

real    9m48.316s

Did it work as expected?

$ file inode_0x888223baee58.dmp
inode_0x888223baee58.dmp: SQLite 3.x database, last written using SQLite version 3039002, file counter 19, database pages 55006, cookie 0x11, schema 4, UTF-8, version-valid-for 19
$ sqlite3 ./inode_0x888223baee58.dmp
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite> .tables
conflicts  db_info    files      obsoletes  packages   provides   requires 

sqlite> select * from files limit 10;
/usr/bin/filan|file|10
/usr/bin/procan|file|10
/usr/bin/socat|file|10
/usr/bin/jose|file|19
/usr/bin/im-chooser|file|23
/usr/bin/db_archive|file|26
/usr/bin/db_checkpoint|file|26
/usr/bin/db_deadlock|file|26
/usr/bin/db_dump|file|26
/usr/bin/db_dump185|file|26

Is the number of available pages vs the inode pages a bug?

Using the respective debug symbols for that kernel, I debugged it using GDB:

gdb> p ((struct inode*)0xffff888223baee58)->i_mapping.nrpages
$3 = 55007
gdb> p ((struct inode*)0xffff888223baee58)->i_size
$4 = 225304576
...
>>> 225304576/4096.0
55006.0

So, it's not a bug.

4 - Sample: 4_4_0-176_206-4_4_0-176-generic.lime

$ time ./vol.py -f ./4_4_0-176_206-4_4_0-176-generic.lime.raw linux.pagecache.Inode --inode 0x880035826958 --dump 
PageVAddr       PagePAddr       MappingAddr     Index   DumpSafe        Flags

real    0m12.048s
...

Did it work as expected?

$ file inode_0x880035826958.dmp
inode_0x880035826958.dmp: Journal file, Mon Mar 30 17:17:01 2020, online, compressed, header size 0xf0, entries 0x17b
$ journalctl --file=inode_0x880035826958.dmp --list-boots
IDX BOOT ID                          FIRST ENTRY                  LAST ENTRY                  
  0 9bf6e4bbdfe243b29e948903f7613c24 Tue 2020-03-31 04:17:01 AEDT Tue 2020-03-31 12:17:01 AEDT
$ journalctl --file=inode_0x880035826958.dmp 
Mar 31 04:17:01 ubuntu-16-04-xenserver65-x64-patched CRON[18822]: (root) CMD (   cd / && run-parts --report /etc/cron.hourly)
Mar 31 04:17:01 ubuntu-16-04-xenserver65-x64-patched CRON[18821]: pam_unix(cron:session): session closed for user root
Mar 31 05:17:01 ubuntu-16-04-xenserver65-x64-patched CRON[18825]: pam_unix(cron:session): session opened for user root by (uid=0)
Mar 31 05:17:01 ubuntu-16-04-xenserver65-x64-patched CRON[18826]: (root) CMD (   cd / && run-parts --report /etc/cron.hourly)
Mar 31 05:17:01 ubuntu-16-04-xenserver65-x64-patched CRON[18825]: pam_unix(cron:session): session closed for user root
...

5 - Sample: blackhat_2022_linux_poc_logkey.lime

$ ./vol.py -f ./blackhat_2022_linux_poc_logkey.lime linux.pagecache.Files | grep 9a590549a728 
0x9a591ef48800.0/       8:1     941200ng0x9a590549a728  REG     20638   18328   -r--r-----      2022-03-31 14:38:42.696390 UTC  2022-03-31 14:38:42.888389 UTC  2022-03-31 14:38:42.888389 UTC    /home/rk/debrkdev/20220331093842/memory/data.lime

Ok.. this is a memory dump.

$ ./vol.py -f ./blackhat_2022_linux_poc_logkey.lime linux.pagecache.Inode --inode 0x9a590549a728 2>/dev/null | wc -l
3275

$ time ./vol.py -f ./blackhat_2022_linux_poc_logkey.lime linux.pagecache.Inode --inode 0x9a590549a728 --dump 2>/tmp/blackhat_2022_linux_poc_logkey.err

real    0m52.983s
...

However, this sample and inode has many errors.

$ wc -l /tmp/blackhat_2022_linux_poc_logkey.err
3459 /tmp/blackhat_2022_linux_poc_logkey.err

$ head /tmp/blackhat_2022_linux_poc_logkey.err 
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87427
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87428
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 89805
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87429
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87430
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87431
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87432
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87433
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87434
ERROR    volatility3.plugins.linux.pagecache: Page out of file bounds: inode 0x9a590549a728, inode size 84529216, page index 87435

6 - Sample: blackhat_2022_poc_ssh_keylogger_test.lime

$ ./vol.py ./blackhat_2022_poc_ssh_keylogger_test.lime linux.pagecache.Files | grep 9a591cbfd1a8 
0x9a591ef48800  /       8:1     944566  0x9a591cbfd1a8  REG     116894  52105   -r--r-----      2022-03-31 15:53:38.761565 UTC  2022-03-31 15:53:39.953558 UTC  2022-03-31 15:53:39.953558 UTC       /home/rk/debrkdev/20220331105338/memory/data.lime

Another memory dump.

Again, this inode have a lot of issues with the page cache.

$ time ./vol.py ./blackhat_2022_poc_ssh_keylogger_test.lime linux.pagecache.Inode --inode 0x9a591cbfd1a8 --dump 2>/tmp/blackhat_2022_poc_ssh_keylogger_test.err

real    1m44.042s
$  wc -l /tmp/blackhat_2022_poc_ssh_keylogger_test.err
2130 /tmp/blackhat_2022_poc_ssh_keylogger_test.err

$ tail /tmp/blackhat_2022_poc_ssh_keylogger_test.err
WARNING  volatility3.framework.symbols.linux.extensions: Cached page at 0xe5dbc05a1040 has a mismatched address space with the inode. Skipping page
WARNING  volatility3.framework.symbols.linux.extensions: Cached page at 0xe5dbc05a1080 has a mismatched address space with the inode. Skipping page
WARNING  volatility3.framework.symbols.linux.extensions: Cached page at 0xe5dbc05a10c0 has a mismatched address space with the inode. Skipping page
WARNING  volatility3.framework.symbols.linux.extensions: Cached page at 0xe5dbc05a1100 has a mismatched address space with the inode. Skipping page
WARNING  volatility3.framework.symbols.linux.extensions: Cached page at 0xe5dbc05a1140 has a mismatched address space with the inode. Skipping page
WARNING  volatility3.framework.symbols.linux.extensions: Cached page at 0xe5dbc05a1180 has a mismatched address space with the inode. Skipping page
WARNING  volatility3.framework.symbols.linux.extensions: Cached page at 0xe5dbc05a11c0 has a mismatched address space with the inode. Skipping page
ERROR    volatility3.framework.symbols.linux: Invalid cached page at 0x9a59064deda0, aborting
WARNING  volatility3.framework.interfaces.plugins: File inode_0x9a591cbfd1a8.dmp could not be written: Invalid cached page at 0x9a59064deda0, aborting
ERROR    volatility3.plugins.linux.pagecache: Error dumping cached pages for inode at 0x9a591cbfd1a8

To ensure everything is correct, I debugged the issue using the crash utility. As you can see, it triggers the exact same error for that inode, causing the listing to abort

crash> files -p 0xffff9a591cbfd1a8
     INODE        NRPAGES
ffff9a591cbfd1a8    52105

      PAGE       PHYSICAL      MAPPING       INDEX CNT FLAGS
ffffe5dbc015f5c0  57d7000 ffff9a591cbfd320    1c6b3  2 ffffc000001028 uptodate,lru,private
...
ffffe5dbc0092c80  24b2000 ffff9a591cbfd320    13eb6  2 ffffc000001028 uptodate,lru,private
files: do_radix_tree: callback operation failed: entry: 7040  item: ffff9a59064deda0
crash> 

Anyway, it still partially recovered the file from memory.

$ strings inode_0x9a591cbfd1a8.dmp | wc -l
98272

$ strings inode_0x9a591cbfd1a8.dmp | grep "^Linux version"
Linux version 4.19.0-9-amd64 (debian-kernel@lists.debian.org) (gcc version 8.3.0 (Debian 8.3.0-6)) #1 SMP Debian 4.19.118-2+deb10u1 (2020-06-07)

7 - Sample: downloads_memory_20220701103641.lime

$ ./vol.py -f ./downloads_memory_20220701103641.lime linux.pagecache.Files | grep 9fa87bf1b1a8 
0x9fa836498000  /       254:1   261475  0x9fa87bf1b1a8  REG     438272  320321  -r--r-----      2022-07-01 14:36:41.535180 UTC  2022-07-01 14:36:53.315186 UTC  2022-07-01 14:36:53.315186 UTC       /home/redacted/downloads/20220701103641.zip
$ time ./vol.py -f ./downloads_memory_20220701103641.lime  linux.pagecache.Inode --inode 0x9fa87bf1b1a8  --dump 2>/tmp/downloads_memory_20220701103641.err

real    0m9.445s
$ cat /tmp/downloads_memory_20220701103641.err
ERROR    volatility3.framework.symbols.linux: Radix Tree node 0x9fa803033240 height 41 exceeds max height of 12
WARNING  volatility3.framework.interfaces.plugins: File inode_0x9fa87bf1b1a8.dmp could not be written: Radix Tree node 0x9fa803033240 height 41 exceeds max height of 12
ERROR    volatility3.plugins.linux.pagecache: Error dumping cached pages for inode at 0x9fa87bf1b1a8

But an empty output file.

$ ls -la inode_0x9fa87bf1b1a8.dmp
-rw------- 1 gmoreira gmoreira 0 Jan 16 20:55 inode_0x9fa87bf1b1a8.dmp

Let's use the crash tool to check if this is a bug in our code.

crash> files -p 0xffff9fa87bf1b1a8
     INODE        NRPAGES
ffff9fa87bf1b1a8   320321

radix_tree_node at ffff9fa87bf1b330
...
files: height 41 is greater than maximum radix tree height index 12

test/test_volatility.py Fixed Show fixed Hide fixed
test/test_volatility.py Fixed Show fixed Hide fixed
test/test_volatility.py Fixed Show fixed Hide fixed
@gcmoreira
Copy link
Contributor Author

@atcuno

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Broken access, instance.pointer.member, to i_link causes backtrace
1 participant