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

ICP encryption tests #17089

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

ICP encryption tests #17089

wants to merge 2 commits into from

Conversation

robn
Copy link
Member

@robn robn commented Feb 24, 2025

Motivation and Context

I wanted to do a good review of #17058, but really had no idea how to without some way to compare the output with what we already have.

And then I went mad and ended up writing a whole correctness and performance test driver for the ICP. And then it found a real (though inconsequential) bug. Good times all round!

Thanks @lowjoel for motivation and rubber-ducking 👍

Description

The main commit adds tests that ensure that the ICP crypto_encrypt() and crypto_decrypt() produce the correct results for all implementations available on this platform.

The actual ZTS scripts are simple drivers for the crypto_test program in it's "correctness" mode. This mode takes a file full of test vectors (inputs and expected outputs), runs them, and checks that the results are expected. It will run the tests for each implementation of the algorithm provided by the ICP.

The test vectors are taken from Project Wycheproof, which provides a huge number of tests, including exercising many edge cases and common implementation mistakes. These tests are provided are JSON files, so a program is included here to convert them into a simpler line-based format for crypto_test to consume.

crypto_test also has a "performance" mode, which will run simple benchmarks against all implementations provded by the ICP and output them for comparison. This is not used by ZTS, but is available to assist with development of new implementations of the underlying primitives.

AVX GCM bug fix

While get it working we found that the AVX GCM was producting the wrong results for IVs != 96 bits (12 bytes). In this case AES-GCM does a couple of GHASH rounds on the provided IV to bring it to 96 bits. The AVX setup did this, but didn't clear the GHASH state before finishing init, so it had leftover state from the IV setup.

Fortunately, OpenZFS always uses 96-bit IVs, so we've avoided this. Just points out more code that we carry but don't use, but that's for another PR.

How Has This Been Tested?

It's all tests!

The ZTS additions are simple wrappers around crypto_test -c <testfile>. Those have been run successfully on Linux and FreeBSD. (note that FreeBSD is still important here, as the ICP is used for userspace crypto).

In "correctness" mode, it runs all the test vectors for each available implementation:

$ ./tests/zfs-tests/cmd/crypto_test -c tests/zfs-tests/tests/functional/crypto/aes_ccm_test.txt
AES-CCM [generic]: tests=552: passed=552 failed=0
AES-CCM [x86_64]: tests=552: passed=552 failed=0
AES-CCM [aesni]: tests=552: passed=552 failed=0
$ ./tests/zfs-tests/cmd/crypto_test -c tests/zfs-tests/tests/functional/crypto/aes_gcm_test.txt
AES-GCM [generic+generic]: tests=316: passed=316 failed=0
AES-GCM [x86_64+generic]: tests=316: passed=316 failed=0
AES-GCM [aesni+generic]: tests=316: passed=316 failed=0
AES-GCM [generic+pclmulqdq]: tests=316: passed=316 failed=0
AES-GCM [x86_64+pclmulqdq]: tests=316: passed=316 failed=0
AES-GCM [aesni+pclmulqdq]: tests=316: passed=316 failed=0
AES-GCM [x86_64+avx]: tests=316: passed=316 failed=0
AES-GCM [aesni+avx]: tests=316: passed=316 failed=0

Because there's currently no way to find out what implementations the ICP has available, and I didn't want to add more to this than necessary, we just try them all. If the platform doesn't support them, it's at least nice about it:

$ uname -m
aarch64
$ ./tests/zfs-tests/cmd/crypto_test -c tests/zfs-tests/tests/functional/crypto/aes_ccm_test.txt
AES-CCM [generic]: tests=552: passed=552 failed=0
AES-CCM [x86_64]: [not supported on this platform]
AES-CCM [aesni]: [not supported on this platform]

If any of the test runs fail, the program will return non-zero, so it wires up to ZTS nicely.

For the performance mode, it'll do the usual thing of run all implementations on various input sizes and show a table:

$ ./tests/zfs-tests/cmd/crypto_test -p AES-GCM
avg encrypt (1000 rounds)        1K     2K     4K     8K    16K    32K    64K   128K   256K   512K
AES-GCM [generic+generic]      12us   24us   48us   95us  190us  387us  808us    1ms    3ms    6ms
AES-GCM [x86_64+generic]       12us   23us   46us   93us  185us  370us  741us    1ms    2ms    5ms
AES-GCM [aesni+generic]         9us   18us   35us   71us  142us  283us  565us    1ms    2ms    4ms
AES-GCM [generic+pclmulqdq]     6us   12us   23us   46us   93us  185us  372us  746us    1ms    2ms
AES-GCM [x86_64+pclmulqdq]      4us    7us   15us   30us   60us  121us  241us  483us    1ms    2ms
AES-GCM [aesni+pclmulqdq]       2us    4us    8us   17us   34us   68us  137us  275us  552us    1ms
AES-GCM [x86_64+avx]          659ns  757ns    1us    1us    3us    6us   13us   26us   52us  105us
AES-GCM [aesni+avx]           565ns  699ns    1us    1us    3us    6us   13us   26us   53us  105us

More benchmarks with the hardware I available here: https://gist.github.com/robn/994f1618b4994b2f90dc8c7838fa5654. Some nice opportunities there!

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:

@lowjoel
Copy link

lowjoel commented Feb 24, 2025

for what it's worth, I'd love to help with the refactor of the ICP code. I (now) at least have some component level understanding, and having some tests there would allow us to refactor a bit more fearlessly. This takes us in the right direction, methinks

@tonyhutter
Copy link
Contributor

Looks like it's passing on the runners 👍

Test: /usr/share/zfs/zfs-tests/tests/functional/crypto/icp_aes_ccm (run as root) [00:00] [PASS]
12:58:34.91 ASSERTION: ICP passes test vectors for AES-CCM                       
12:58:34.93 AES-CCM [generic]: tests=552: passed=552 failed=0                    
12:58:34.93 AES-CCM [x86_64]: tests=552: passed=552 failed=0                     
12:58:34.93 AES-CCM [aesni]: tests=552: passed=552 failed=0                      
12:58:34.94 SUCCESS: crypto_test -c /usr/share/zfs/zfs-tests/tests/functional/crypto/aes_ccm_test.txt
12:58:34.94 ICP passes test vectors for AES-CCM                                  
Test: /usr/share/zfs/zfs-tests/tests/functional/crypto/icp_aes_gcm (run as root) [00:00] [PASS]
12:58:35.05 ASSERTION: ICP passes test vectors for AES-GCM                       
12:58:35.08 AES-GCM [generic+generic]: tests=316: passed=316 failed=0            
12:58:35.08 AES-GCM [x86_64+generic]: tests=316: passed=316 failed=0             
12:58:35.08 AES-GCM [aesni+generic]: tests=316: passed=316 failed=0              
12:58:35.08 AES-GCM [generic+pclmulqdq]: tests=316: passed=316 failed=0          
12:58:35.08 AES-GCM [x86_64+pclmulqdq]: tests=316: passed=316 failed=0           
12:58:35.08 AES-GCM [aesni+pclmulqdq]: tests=316: passed=316 failed=0            
12:58:35.08 AES-GCM [x86_64+avx]: tests=316: passed=316 failed=0                 
12:58:35.08 AES-GCM [aesni+avx]: tests=316: passed=316 failed=0                  
12:58:35.08 SUCCESS: crypto_test -c /usr/share/zfs/zfs-tests/tests/functional/crypto/aes_gcm_test.txt
12:58:35.08 ICP passes test vectors for AES-GCM                                  

robn added 2 commits February 25, 2025 13:08
This commit adds tests that ensure that the ICP crypto_encrypt() and
crypto_decrypt() produce the correct results for all implementations
available on this platform.

The actual ZTS scripts are simple drivers for the crypto_test program in
it's "correctness" mode. This mode takes a file full of test vectors
(inputs and expected outputs), runs them, and checks that the results
are expected. It will run the tests for each implementation of the
algorithm provided by the ICP.

The test vectors are taken from Project Wycheproof, which provides a
huge number of tests, including exercising many edge cases and common
implementation mistakes. These tests are provided are JSON files, so a
program is included here to convert them into a simpler line-based
format for crypto_test to consume.

crypto_test also has a "performance" mode, which will run simple
benchmarks against all implementations provded by the ICP and output
them for comparison. This is not used by ZTS, but is available to assist
with development of new implementations of the underlying primitives.

Thanks-to: Joel Low <[email protected]>
Sponsored-by: https://despairlabs.com/sponsor/
Signed-off-by: Rob Norris <[email protected]>
IVs != 96 bits get hashed with GHASH to bring them to 96 bits. Any call
to GHASH will mix the ghash state in gcm_ghash. This is expected to be
zero at first use in an encrypt or decrypt operation, so it needs to be
zeroed after using GHASH in setup.

gcm_init() does this, but gcm_avx_init() zeroed it before setup, not
after, resulting in incorrect encrypt/decrypt results when using AVX GCM
with an IV != 96 bits.

OpenZFS _always_ uses a 96 bit IV (ZIO_DATA_IV_LEN) so this will never
have been hit in any real-world use, which is extremely fortunate, as we
would have incorrectly-encrypted data on-disk. Still, as long as we have
this code here we should make sure it's correct.

Thanks-to: Joel Low <[email protected]>
Sponsored-by: https://despairlabs.com/sponsor/
Signed-off-by: Rob Norris <[email protected]>
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.

3 participants