Skip to content

linux: a few fixes of callback typecasting (for the upcoming ClangCFI) #12260

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

Merged
merged 8 commits into from
Jul 20, 2021
Merged

linux: a few fixes of callback typecasting (for the upcoming ClangCFI) #12260

merged 8 commits into from
Jul 20, 2021

Conversation

solbjorn
Copy link
Contributor

@solbjorn solbjorn commented Jun 20, 2021

Motivation and Context

ClangCFI (Control Flow Integrity) for ARM64 was accepted upstream during 5.13 merge window and will be widely available with 5.13 stable release.
ClangCFI for x86 is currently WIP, hopefully will hit 5.14 cycle.

CFI strongly prohibits callbacks typecasting as a part of possible code exploits/attacks.
On default configuration, every callback type mismatch leads to kernel panic, in "permissive" mode (which is designed for development and hunting mismatches/casting) this results in kernel warning splat. This can become a problem when ZFS will receive 5.13+ support.

Typical CFI failure splat in "permissive" mode:

[  535.456659] CFI failure (target: zio_execute.cfi_jt+0x0/0x8 [zfs]):
[  535.456820] Call Trace:
[  535.456822]  ? __cfi_check+0x1a7d/0x1e30 [zfs]
[  535.456882]  ? taskq_thread+0x6ce/0x750 [spl]
[  535.456893]  ? autoremove_wake_function.cfi_jt+0x8/0x8 [spl]
[  535.456900]  ? zil_lwb_write_done.cfi_jt+0x8/0x8 [zfs]
[  535.456960]  ? __thread_create.cfi_jt+0x10/0x10 [spl]
[  535.456967]  ? kthread+0x234/0x260
[  535.456969]  ? __thread_create.cfi_jt+0x10/0x10 [spl]
[  535.456976]  ? kthreadd.cfi_jt+0x8/0x8
[  535.456979]  ? ret_from_fork+0x22/0x30
[  535.456981] ---[ end trace 90952a0add50c989 ]---

Description

This PR might not cover 100% of callback type mismatches in ZFS kernel code, but at least fixes CFI failures on common tasks (pool/dataset creation/editing, booting Linux from ZFS root etc.).
The actual "fixes" are typecasting removals within as minimal changes as possible.

Covered code / CFI failures:

CFI failure (target: zio_execute.cfi_jt+0x0/0x8 [zfs]):
CFI failure (target: zpl_readpage.cfi_jt+0x0/0x10 [zfs]):
CFI failure (target: zil_itxg_clean.cfi_jt+0x0/0x10 [zfs]):
CFI failure (target: fnvlist_free.cfi_jt+0x0/0x10 [zfs]):
CFI failure (target: iput.cfi_jt+0x0/0x8 [zfs]):
CFI failure (target: xdr_u_longlong_t.cfi_jt+0x0/0x10 [znvpair]):
CFI failure (target: zio_interrupt.cfi_jt+0x0/0x8 [zfs]):
CFI failure (target: zvol_free.cfi_jt+0x0/0x8 [zfs]):

How Has This Been Tested?

  • booting Linux 5.12.12 on x86_64 with backported ClangCFI for ARM64 and x86 in "permissive" mode;
  • running common ZFS operations and tracking down CFI failure splats in kernel logs;
  • fixing the corresponding code;
  • booting in CFI "strict" mode and ensuring that the system works as expected;
  • running ZTS on x86_64 with CFI in "permissive" mode;
  • seeking for CFI failures in the kernel logs and fixing;
  • ensuring that they don't happen anymore.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Library ABI change (libzfs, libzfs_core, libnvpair, libuutil and libzfsbootenv)
  • Documentation (a change to man pages or other documentation)

Checklist:

Copy link
Contributor

@behlendorf behlendorf left a comment

Choose a reason for hiding this comment

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

Interesting. Thanks for giving us a heads up on this so we can make the needed changes. There are likely a few more less common call paths in the code which we'll need to update. If you have easy access to a dedicated test system, and are so inclined, you could probably uncover the rest of these by locally running the ZFS Test Suite. The basic recipe can be found in the .github/workflows/zfs-tests-functional.yml file in the zfs repository. The test suite does perform some destructive operations, to to be extra safe I'd just make sure not to run it on a system which contains a pool with real data.

}

static int
zpl_readpage_filler(void *data, struct page *page)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: let's use pp for the page variable just for consistency with zpl_readpage().

@behlendorf behlendorf added the Status: Code Review Needed Ready for review and testing label Jun 22, 2021
@solbjorn
Copy link
Contributor Author

Yeah, I think I could test it with ZFS Suite to catch more. I'll update this PR then, thanks!

@solbjorn
Copy link
Contributor Author

@behlendorf, thanks for the idea, you was right. I've triggered some additional paths during the execution of ZFS Test Suite:

> grep 'CFI failure' log | cut -d';' -f2- | sort -u
CFI failure (target: fnvlist_free.cfi_jt+0x0/0x10 [zfs]):
CFI failure (target: iput.cfi_jt+0x0/0x8 [zfs]):
CFI failure (target: xdr_u_longlong_t.cfi_jt+0x0/0x10 [znvpair]):
CFI failure (target: zio_interrupt.cfi_jt+0x0/0x8 [zfs]):
CFI failure (target: zvol_free.cfi_jt+0x0/0x8 [zfs]):

Rough total amount of failures during ZTS cycle:

> grep 'CFI failure' log | cut -d';' -f2- | wc -l  
1602

I'll update this PR till the end of this week by covering the abovementioned code.

solbjorn added 4 commits June 26, 2021 22:02
Instead of declaring zio_{,re}execute() as void (*)(zio_t *zio) and
then typecasting it to void (*)(void *arg) when adding a taskq,
declare it with void *arg initially.
This implies no functional changes, but eliminates callback
typecasting / type violation, which may be fatal in case of
ClangCFI.

Signed-off-by: Alexander Lobakin <[email protected]>
Just declare zil_itxg_clean() with void *arg instead of itxs_t * to
be able to pass it directly to taskq_dispatch() without typecasting.
No functional changes, but critical for passing ClangCFI checks.

Signed-off-by: Alexander Lobakin <[email protected]>
To avoid callback typecast violations (and e.g. triggering ClangCFI),
declare separate callbacks for read_cache_pages() and
zpl_address_space_operations:

- convert zpl_readpage() to a static inline zpl_readpage_common(),
  which now takes only one argument instead of two (the first was
  never used in the code);
- declare zpl_readpage() for zpl_address_space_operations and
  zpl_readpage_filler() to pass it as read_cache_pages() agrument.
  They differ only by the type of the first argument (struct file *
  vs void *), and both just inline zpl_readpage_common().
- Pass NULL as the last argument (void *data) of read_cache_pages()
  call as zpl_readpage_filler() (former zpl_readpage()) doesn't use
  it.

Signed-off-by: Alexander Lobakin <[email protected]>
Just declare zio_interrupt() as void (*)(void *) to match
task_func_t type and pass callback typecheck. This doesn't
change anything for the direct callers.

Signed-off-by: Alexander Lobakin <[email protected]>
@solbjorn
Copy link
Contributor Author

Updated. Now covers several more cases:

CFI failure (target: fnvlist_free.cfi_jt+0x0/0x10 [zfs]):
CFI failure (target: iput.cfi_jt+0x0/0x8 [zfs]):
CFI failure (target: xdr_u_longlong_t.cfi_jt+0x0/0x10 [znvpair]):
CFI failure (target: zio_interrupt.cfi_jt+0x0/0x8 [zfs]):
CFI failure (target: zvol_free.cfi_jt+0x0/0x8 [zfs]):

and passes the entire ZTS with not a single CFI failure.
checkstyle warning and a review comment also were addressed.

@behlendorf
Copy link
Contributor

@solbjorn this is looking good! It looks like there's perhaps just one last cstyle warning to resolve.

solbjorn added 4 commits June 29, 2021 21:56
At the moment, nvs_xdr_nvp_op() passes several static inlines
as callbacks for xdr_array(). This implicitly creates static
functions for each of them to be able to retrieve their addresses.
However, all those functions have different prototypes incompatible
with xdrproc_t which involves callback typecasting and CFI failures.
Just declare the callbacks explicitly via a shorthand helper. This
doesn't increase the codesize, but allows to pass function addresses
directly to xdr_array().

Signed-off-by: Alexander Lobakin <[email protected]>
Wrap the external iput() into a static callback and pass it to
taskq_dispatch() instead of passing it directly with typecasting.

Signed-off-by: Alexander Lobakin <[email protected]>
Wrap it into a static function and pass it direcly as
a zcp_cleanup_t callback.

Signed-off-by: Alexander Lobakin <[email protected]>
Wrap it into a static function and pass it as a task callback
instead.
ops is dereferenced prior to this, so check for ops != NULL is
not needed.

Signed-off-by: Alexander Lobakin <[email protected]>
@solbjorn
Copy link
Contributor Author

Updated. Those are xdr_u_short() and xdr_u_int() function suffixes, not the real type usages, so I suppressed these two false-positives with CSTYLED.

@solbjorn
Copy link
Contributor Author

Ping? Was approved 19 days ago...

@mmaybee mmaybee added Status: Accepted Ready to integrate (reviewed, tested) and removed Status: Code Review Needed Ready for review and testing labels Jul 20, 2021
@mmaybee mmaybee merged commit 23c13c7 into openzfs:master Jul 20, 2021
@solbjorn solbjorn deleted the fixes-for-cfi branch July 22, 2021 08:57
behlendorf pushed a commit to behlendorf/zfs that referenced this pull request Aug 23, 2021
* zio: avoid callback typecasting
* zil: avoid zil_itxg_clean() callback typecasting
* zpl: decouple zpl_readpage() into two separate callbacks
* nvpair: explicitly declare callbacks for xdr_array()
* linux/zfs_nvops: don't use external iput() as a callback
* zcp_synctask: don't use fnvlist_free() as a callback
* zvol: don't use ops->zv_free() as a callback for taskq_dispatch()

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Mark Maybee <[email protected]>
Signed-off-by: Alexander Lobakin <[email protected]>
Closes openzfs#12260
behlendorf pushed a commit to behlendorf/zfs that referenced this pull request Aug 24, 2021
* zio: avoid callback typecasting
* zil: avoid zil_itxg_clean() callback typecasting
* zpl: decouple zpl_readpage() into two separate callbacks
* nvpair: explicitly declare callbacks for xdr_array()
* linux/zfs_nvops: don't use external iput() as a callback
* zcp_synctask: don't use fnvlist_free() as a callback
* zvol: don't use ops->zv_free() as a callback for taskq_dispatch()

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Mark Maybee <[email protected]>
Signed-off-by: Alexander Lobakin <[email protected]>
Closes openzfs#12260
behlendorf pushed a commit to behlendorf/zfs that referenced this pull request Aug 24, 2021
* zio: avoid callback typecasting
* zil: avoid zil_itxg_clean() callback typecasting
* zpl: decouple zpl_readpage() into two separate callbacks
* nvpair: explicitly declare callbacks for xdr_array()
* linux/zfs_nvops: don't use external iput() as a callback
* zcp_synctask: don't use fnvlist_free() as a callback
* zvol: don't use ops->zv_free() as a callback for taskq_dispatch()

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Mark Maybee <[email protected]>
Signed-off-by: Alexander Lobakin <[email protected]>
Closes openzfs#12260
behlendorf pushed a commit to behlendorf/zfs that referenced this pull request Aug 24, 2021
* zio: avoid callback typecasting
* zil: avoid zil_itxg_clean() callback typecasting
* zpl: decouple zpl_readpage() into two separate callbacks
* nvpair: explicitly declare callbacks for xdr_array()
* linux/zfs_nvops: don't use external iput() as a callback
* zcp_synctask: don't use fnvlist_free() as a callback
* zvol: don't use ops->zv_free() as a callback for taskq_dispatch()

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Mark Maybee <[email protected]>
Signed-off-by: Alexander Lobakin <[email protected]>
Closes openzfs#12260
behlendorf pushed a commit that referenced this pull request Aug 31, 2021
* zio: avoid callback typecasting
* zil: avoid zil_itxg_clean() callback typecasting
* zpl: decouple zpl_readpage() into two separate callbacks
* nvpair: explicitly declare callbacks for xdr_array()
* linux/zfs_nvops: don't use external iput() as a callback
* zcp_synctask: don't use fnvlist_free() as a callback
* zvol: don't use ops->zv_free() as a callback for taskq_dispatch()

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Mark Maybee <[email protected]>
Signed-off-by: Alexander Lobakin <[email protected]>
Closes #12260
tonyhutter pushed a commit to tonyhutter/zfs that referenced this pull request Sep 15, 2021
* zio: avoid callback typecasting
* zil: avoid zil_itxg_clean() callback typecasting
* zpl: decouple zpl_readpage() into two separate callbacks
* nvpair: explicitly declare callbacks for xdr_array()
* linux/zfs_nvops: don't use external iput() as a callback
* zcp_synctask: don't use fnvlist_free() as a callback
* zvol: don't use ops->zv_free() as a callback for taskq_dispatch()

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Mark Maybee <[email protected]>
Signed-off-by: Alexander Lobakin <[email protected]>
Closes openzfs#12260
datacore-rm pushed a commit to DataCoreSoftware/openzfs that referenced this pull request Feb 9, 2024
* zio: avoid callback typecasting
* zil: avoid zil_itxg_clean() callback typecasting
* zpl: decouple zpl_readpage() into two separate callbacks
* nvpair: explicitly declare callbacks for xdr_array()
* linux/zfs_nvops: don't use external iput() as a callback
* zcp_synctask: don't use fnvlist_free() as a callback
* zvol: don't use ops->zv_free() as a callback for taskq_dispatch()

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Mark Maybee <[email protected]>
Signed-off-by: Alexander Lobakin <[email protected]>
Closes openzfs#12260
datacore-rm pushed a commit to DataCoreSoftware/openzfs that referenced this pull request Feb 28, 2024
* zio: avoid callback typecasting
* zil: avoid zil_itxg_clean() callback typecasting
* zpl: decouple zpl_readpage() into two separate callbacks
* nvpair: explicitly declare callbacks for xdr_array()
* linux/zfs_nvops: don't use external iput() as a callback
* zcp_synctask: don't use fnvlist_free() as a callback
* zvol: don't use ops->zv_free() as a callback for taskq_dispatch()

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Mark Maybee <[email protected]>
Signed-off-by: Alexander Lobakin <[email protected]>
Closes openzfs#12260
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Accepted Ready to integrate (reviewed, tested)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants