Skip to content

Conversation

@eve-mem
Copy link
Contributor

@eve-mem eve-mem commented Mar 28, 2025

Hello 👋

This PR updates volshell display types to follow pointers and display more information, and IMHO makes it easier to use.

This is a replacement for PR #1028 as my git-fu is not strong enough to survive a rebase.

Fixes #1714
Fixes #1705

dt() updates

dt() will now follow pointers as needed to display information, up to a maximum of MAX_DEREFERENCE_COUNT times (currently set to 4).

With the a dt() of a task_struct you get a small hint at the changes. Here we see that stack and files are actually pointers as denoted by the * - we can also see what kind of struct it is, e.g. files is a files_struct . Previously it would have only displayed pointer so you wouldn't know what it was. The value displayed is still the actual pointer data as before. It will show pointers in hex e.g. 0x880001f60000 rather than 149533614276608 as this feels more natural.

The @ 0x88001d06c800 also shows where in the layer this object is. Previously you'd need to use .vol.offset or similar to find this out as an extra step.

symbol_table_name1!task_struct (1784 bytes) @ 0x88001d06c800:
    0x0 :   state                           symbol_table_name1!long int                     1
    0x8 :   stack                           *symbol_table_name1!void                        0x880001f60000
   0x10 :   usage                           symbol_table_name1!atomic_t                     0x88001d06c810
   0x14 :   flags                           symbol_table_name1!unsigned int                 4202752
   0x18 :   ptrace                          symbol_table_name1!unsigned int                 0
<snip>
  0x480 :   files                           *symbol_table_name1!files_struct                0x88001cbd59c0
<snip>

However things get useful when we would need to follow pointers, e.g. the files member of task_struct

Here we can see that .files is a pointer. It was located at offset 0x88001d06cc80 and points to 0x88001cbd59c0. At 0x88001cbd59c0 we have the files_struct and dt will display the details of it. The results for files_struct is indented to show that it is the result of following a pointer. Previously if you did dt(task.files) you would simply be told it was a pointer, you'd need to type dt(task.files.dereference()) to get the results. I personally struggle to spell dereference correctly most of the time and these changes make my life a little easier!

(layer_name) >>> dt(gp(pid=8497).files)
symbol_table_name1!pointer (8 bytes) @ 0x88001d06cc80 -> 0x88001cbd59c0
    symbol_table_name1!files_struct (704 bytes) @ 0x88001cbd59c0:
       0x0 :   count                  symbol_table_name1!atomic_t            0x88001cbd59c0
       0x8 :   fdt                    *symbol_table_name1!fdtable            0x88001cbd59d0
      0x10 :   fdtab                  symbol_table_name1!fdtable             0x88001cbd59d0
      0x80 :   file_lock              symbol_table_name1!spinlock_t          0x88001cbd5a40
      0x84 :   next_fd                symbol_table_name1!int                 1
      0x88 :   close_on_exec_init     symbol_table_name1!embedded_fd_set     0x88001cbd5a48
      0x90 :   open_fds_init          symbol_table_name1!embedded_fd_set     0x88001cbd5a50

The changes will follow multiple pointers if needed too. If we look at the fdt here we can see that the fd member is a double pointer which eventually gets to a file as denoted by the **.

(layer_name) >>> dt(gp(pid=8497).files.fdt)
symbol_table_name1!pointer (8 bytes) @ 0x88001cbd59c8 -> 0x88001cbd59d0
    symbol_table_name1!fdtable (56 bytes) @ 0x88001cbd59d0:
       0x0 :   max_fds           symbol_table_name1!unsigned int         64
       0x8 :   fd                **symbol_table_name1!file               0x88001cbd5a58
      0x10 :   close_on_exec     *symbol_table_name1!__kernel_fd_set     0x88001cbd5a48
      0x18 :   open_fds          *symbol_table_name1!__kernel_fd_set     0x88001cbd5a50
      0x20 :   rcu               symbol_table_name1!rcu_head             0x88001cbd59f0
      0x30 :   next              *symbol_table_name1!fdtable             0x0 (null pointer)

Lastly we can see fd just as easily by using dt(task.files.fdt.fd). Here we see the first pointer located at 0x88001cbd59d8 points to 0x88001cbd5a58. The second pointer at 0x88001cbd5a58 then points to 0x88001d145580. Finally at 0x88001d145580 we have the file itself. You still get all the information about the pointers themselves if that is what you're interested in but you quickly get to the struct information which is what I think is the more common use case.

(layer_name) >>> dt(gp(pid=8497).files.fdt.fd)
symbol_table_name1!pointer (8 bytes) @ 0x88001cbd59d8 -> 0x88001cbd5a58
    symbol_table_name1!pointer (8 bytes) @ 0x88001cbd5a58 -> 0x88001d145580
        symbol_table_name1!file (208 bytes) @ 0x88001d145580:
           0x0 :   f_u               symbol_table_name1!unnamed_6733425fe3c7f4c9     0x88001d145580
          0x10 :   f_path            symbol_table_name1!path                         0x88001d145590
          0x20 :   f_op              *symbol_table_name1!file_operations             0xffff8143aa70
          0x28 :   f_lock            symbol_table_name1!spinlock_t                   0x88001d1455a8
          0x2c :   f_sb_list_cpu     symbol_table_name1!int                          0
          0x30 :   f_count           symbol_table_name1!atomic64_t                   0x88001d1455b0
          0x38 :   f_flags           symbol_table_name1!unsigned int                 32768
          0x3c :   f_mode            symbol_table_name1!unsigned int                 29
          0x40 :   f_pos             symbol_table_name1!long long int                0
          0x48 :   f_owner           symbol_table_name1!fown_struct                  0x88001d1455c8
          0x68 :   f_cred            *symbol_table_name1!cred                        0x88001ca33cc0
          0x70 :   f_ra              symbol_table_name1!file_ra_state                0x88001d1455f0
          0x90 :   f_version         symbol_table_name1!long long unsigned int       0
          0x98 :   f_security        *symbol_table_name1!void                        0x0 (null pointer)
          0xa0 :   private_data      *symbol_table_name1!void                        0x0 (null pointer)
          0xa8 :   f_ep_links        symbol_table_name1!list_head                    0x88001d145628
          0xb8 :   f_tfile_llink     symbol_table_name1!list_head                    0x88001d145638
          0xc8 :   f_mapping         *symbol_table_name1!address_space               0x88001f9b0480

Fixing #1705

Before the changes, you cannot dt() a file_operations struct as it includes write which is a python function for the vol object.

(layer_name) >>> dt(gp(pid=8497).files.fd_array[0].f_op.dereference())
symbol_table_name1!file_operations (208 bytes)
  0x0 :   owner                 symbol_table_name1!pointer     0
  0x8 :   llseek                symbol_table_name1!pointer     281472848377746
 0x10 :   read                  symbol_table_name1!pointer     281472848377728
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/eve/Documents/volatility3/volatility3/cli/volshell/linux.py", line 147, in display_type
    return super().display_type(object, offset)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eve/Documents/volatility3/volatility3/cli/volshell/generic.py", line 461, in display_type
    self._display_value(getattr(volobject, member)),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eve/Documents/volatility3/volatility3/cli/volshell/generic.py", line 481, in _display_value
    return hex(value.vol.offset)
               ^^^^^^^^^
AttributeError: 'function' object has no attribute 'vol'

With the change to use .member() as suggested by @atcuno (#1714 (comment)) it now works as expected:

(layer_name) >>> dt(gp(pid=8497).files.fd_array[0].f_op)
symbol_table_name1!pointer (8 bytes) @ 0x88001d1455a0 -> 0xffff8143aa70
    symbol_table_name1!file_operations (208 bytes) @ 0xffff8143aa70:
       0x0 :   owner                 *symbol_table_name1!module       0x0 (null pointer)
       0x8 :   llseek                *symbol_table_name1!function     0xffff81243792
      0x10 :   read                  *symbol_table_name1!function     0xffff81243780
      0x18 :   write                 *symbol_table_name1!function     0xffff81243783
      0x20 :   aio_read              *symbol_table_name1!function     0x0 (null pointer)
      0x28 :   aio_write             *symbol_table_name1!function     0x0 (null pointer)
      0x30 :   readdir               *symbol_table_name1!function     0x0 (null pointer)
      0x38 :   poll                  *symbol_table_name1!function     0x0 (null pointer)
      0x40 :   unlocked_ioctl        *symbol_table_name1!function     0x0 (null pointer)
      0x48 :   compat_ioctl          *symbol_table_name1!function     0x0 (null pointer)
      0x50 :   mmap                  *symbol_table_name1!function     0x0 (null pointer)
      0x58 :   open                  *symbol_table_name1!function     0x0 (null pointer)
      0x60 :   flush                 *symbol_table_name1!function     0x0 (null pointer)
      0x68 :   release               *symbol_table_name1!function     0x0 (null pointer)
      0x70 :   fsync                 *symbol_table_name1!function     0x0 (null pointer)
      0x78 :   aio_fsync             *symbol_table_name1!function     0x0 (null pointer)
      0x80 :   fasync                *symbol_table_name1!function     0x0 (null pointer)
      0x88 :   lock                  *symbol_table_name1!function     0x0 (null pointer)
      0x90 :   sendpage              *symbol_table_name1!function     0x0 (null pointer)
      0x98 :   get_unmapped_area     *symbol_table_name1!function     0x0 (null pointer)
      0xa0 :   check_flags           *symbol_table_name1!function     0x0 (null pointer)
      0xa8 :   flock                 *symbol_table_name1!function     0x0 (null pointer)
      0xb0 :   splice_write          *symbol_table_name1!function     0xffff81243a77
      0xb8 :   splice_read           *symbol_table_name1!function     0x0 (null pointer)
      0xc0 :   setlease              *symbol_table_name1!function     0x0 (null pointer)
      0xc8 :   fallocate             *symbol_table_name1!function     0x0 (null pointer)

Fixing #1714

Previously if you used dt() on a struct where a member was paged out it would backtrace and show no future results. As per @atcuno suggestion it will now just display N/A and continue. Here is a short example with a nonsensical dt at 0x0.

(layer_name) >>> dt('task_struct', 0x0)
symbol_table_name1!task_struct (1784 bytes) @ 0x0:
    0x0 :   state                           symbol_table_name1!long int                   N/A
    0x8 :   stack                           symbol_table_name1!pointer                    N/A
<snip>

Let me know what you think! Huge thanks to @atcuno and @ikelos for the encouragement. 🙏

eve-mem added 5 commits March 28, 2025 11:45
- Introduced `_get_type_name_with_pointer` to properly display pointer types.
- Enhanced `display_type` to follow and display pointer chains up to `MAX_DEREFERENCE_COUNT` levels.
- Added `_display_simple_type` to standardize type information display.
- Improved `_display_value` to highlight null and unreadable pointers.
!)

Details:
    Implements exception handling for InvalidAddressException in volshell.
    Ensures invalid pointers don't cause large stack traces, displaying "N/A" instead.
…er fuctions

Previously, `getattr(volobject, member)` in `display_type()` would incorrectly
retrieve method references (e.g., `.write`) instead of the intended object
addresses, causing an `AttributeError` when `_display_value()` attempted to
access `.vol.offset`.

This commit replaces `getattr(volobject, member)` with `volobject.member(member)`,
ensuring that the correct object address is retrieved instead of method references.

Fixes: volatilityfoundation#1705
Replaced `member_type.vol.object_class == objects.Pointer` with `isinstance(member_type, objects.Pointer)`
to identify pointer types consistently. Thanks to @ikelos for the suggestion!
Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! I think the last _display_value function is a little brittle in places, but once that's hardended up a bit, this should be good to go in! 5:D

Copy link
Member

@ikelos ikelos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, feels better to have made the distinction on type early on... 5:)

@eve-mem
Copy link
Contributor Author

eve-mem commented Apr 3, 2025

I'll try and get those merge conflicts fixed for tomorrow. Thanks for the help on those other bits @dgmcdona and @ikelos

if self.context.layers[self.current_layer].is_valid(
value.vol.offset
):
return f"offset: {hex(value.vol.offset)}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think this can be 0x{value.vol.offset:x} rather than {hex(value.vol.offset)}. Absolutely a stylistic choice though... 5;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind either way - would you rather 0x{value.vol.offset:x}? (can add it to the style guide PR?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think so? Using built in format modifiers seems better than a separate function call to convert it? Sure, I'll get it added to the coding style...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense - done! 😄

@ikelos ikelos merged commit 6ddb0fe into volatilityfoundation:develop Apr 3, 2025
14 checks passed
@eve-mem eve-mem mentioned this pull request May 23, 2025
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.

volshell dt() throws exception on invalid pointer members Volshell display_type() issue on .write attribute

3 participants