From mneumann at ntecs.de Tue Dec 24 17:00:55 2024 From: mneumann at ntecs.de (Michael Neumann) Date: Wed, 25 Dec 2024 02:00:55 +0100 Subject: crypto(9) removal and refactoring dm_target_crypt Message-ID: Hi, Here is a massive patch: https://leaf.dragonflybsd.org/~mneumann/0001-Retire-crypto-9-and-tcplay-8-and-refactor-dm_target_.patch I'd like to get some feedback concerning: * the workqueue implementation found in sys/dev/dm/crypto/dm_target_crypt.c. It's a "simple" multi-producer single consumer queue with a pre-allocated free list, so 100% non-allocating when it's in use. The worker threads "own" the memory used to process the crypto requests, so we no longer need malloc_pipe to allocate the big buffers from at each BIO request. * bogus page handling: e.g. in dmtc_bio_read_decrypt_job_do(), which decrypts the read bio data and runs in the worker thread, I unconditionally memcpy(data_buf, bio->bio_buf->b_data), in order to avoid decrypting in-place. After decryption, I memcpy the data_buf back to bio->bio_buf->b_data. I don't see the difference in decrypting in-place, vs. copy back... Maybe someone can enligthen me :) The code before mentioned the bogus page handling, but it was disabled. The relevant comment: * For reads with bogus page we can't decrypt in place as stuff * can get ripped out from under us. * * XXX actually it looks like we can, and in any case the initial * read already completed and threw crypted data into the buffer * cache buffer. Disable for now. Same in dmtc_bio_write_encrypt_job_do(), which processes a BIO write request. Here, I unconditionally memcpy from bio->bio_buf->b_data to data_buf again, then encrypt data_buf, and then, rewire tht bio->bio_buf->b_data to data_buf before calling vn_strategy. In this case, copying back the encrypted data_buf to bio->bio_buf->b_data is avoided. But this comes at the cost of having to block the worker thread until vn_strategy finishes and calls bio->bio_done. It has to wait, because it owns data_buf and cannot handle the next request until it is completely written. A memcpy back to the original bio->bio_buf would simply that code, as we could immediatly process the next request, and the bio_done callback would simply call biodone(bio). * The "new" cypto API as found in crypto/crypto_cipher.{h,c}. It's only used by dm_target_crypt, and basically consists of struct crypto_cipher definitions for each crypto algorithm/implementation (e.g. AES-CBC or AES-XTS w/ and w/o AESNI). Each crypto_cipher comes with a "probe" function, in order to check if the cipher is "available", mainly to support hardware AESNI and fall back to software implementations. To find a "struct crypto_cipher", simply call e.g. struct crypto_cipher *cipher = crypto_cipher_find("aes", "cbc", klen_in_bits); and then you can use this struct to set the key and call encrypt / decrypt functions. The crypto_cipher_context contains the keys required for encryption/decryption. And the crypto_cipher_iv, for the initialization vector. They are both unions that contain all "context"s or "iv"s of all supported algorithms, so adding AES-512, if that exists, would grow the structs. Of course, you could also dynamically allocate it. The old crypto(9) framework had the same approach only for the IV, but the context had to be dynamically allocated. In case you test, please don't do this with very important data. While I have tested it quite a bit, it's always better to have backups :) Feel free to torture test this either using cryptsetup luksFormat --cipher aes-cbc-essiv:sha256 or --cipher aes-xts-essiv:sha256. And also sysctl hw.aesni_disable=0 or 1. Best regards, Michael -- Michael Neumann NTECS Consulting www.ntecs.de