From 728036f25635a9b14310eded1761cf6cd0bacb1a Mon Sep 17 00:00:00 2001 From: Sebastien Buisson Date: Wed, 17 Jun 2020 16:03:04 +0000 Subject: [PATCH] LU-12275 sec: O_DIRECT for encrypted file Add O_DIRECT support for encrypted files. By default, fscrypt does not support O_DIRECT because it needs pagecache pages to proceed. With Lustre, we can make use of pages being used for sending RPCs. They can be twisted so that they have a proper mapping and index, suitable for encryption/decryption. One of the benefits of O_DIRECT support for encrypted files is that we get support for mirroring at the same time. Test-Parameters: testlist=sanity-sec envdefinitions=ONLY="36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 52" clientdistro=el8.1 fstype=ldiskfs mdscount=2 mdtcount=4 Test-Parameters: testlist=sanity-sec envdefinitions=ONLY="36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 52" clientdistro=el8.1 fstype=zfs mdscount=2 mdtcount=4 Signed-off-by: Sebastien Buisson Change-Id: I12f61c44b55f3a454f38016200f81eb735ab8f18 Reviewed-on: https://review.whamcloud.com/38967 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Andreas Dilger Reviewed-by: Wang Shilong Reviewed-by: Oleg Drokin --- .../client_side_encryption/access_semantics.txt | 3 - lustre/llite/llite_lib.c | 10 +- lustre/llite/rw26.c | 27 ++++- lustre/obdclass/cl_io.c | 12 +++ lustre/osc/osc_request.c | 37 +++++-- lustre/tests/sanity-sec.sh | 112 +++++++++++++++++++-- 6 files changed, 175 insertions(+), 26 deletions(-) diff --git a/Documentation/client_side_encryption/access_semantics.txt b/Documentation/client_side_encryption/access_semantics.txt index fe2c28d..7ed0bc7 100644 --- a/Documentation/client_side_encryption/access_semantics.txt +++ b/Documentation/client_side_encryption/access_semantics.txt @@ -42,9 +42,6 @@ astute users may notice some differences in behavior: may be used to overwrite the source files but isn't guaranteed to be effective on all filesystems and storage devices. -- Direct I/O is not supported on encrypted files. Attempts to use - direct I/O on such files will fall back to buffered I/O. - - The fallocate operations FALLOC_FL_COLLAPSE_RANGE, FALLOC_FL_INSERT_RANGE, and FALLOC_FL_ZERO_RANGE are not supported on encrypted files and will fail with EOPNOTSUPP. diff --git a/lustre/llite/llite_lib.c b/lustre/llite/llite_lib.c index 4c647df..a3b4a04 100644 --- a/lustre/llite/llite_lib.c +++ b/lustre/llite/llite_lib.c @@ -2035,7 +2035,15 @@ int ll_setattr_raw(struct dentry *dentry, struct iattr *attr, attr->ia_valid & ATTR_SIZE) { xvalid |= OP_XVALID_FLAGS; flags = LUSTRE_ENCRYPT_FL; - if (attr->ia_size & ~PAGE_MASK) { + /* Call to ll_io_zero_page is not necessary if + * truncating on PAGE_SIZE boundary, because + * whole pages will be wiped. + * In case of Direct IO, all we need is to set + * new size. + */ + if (attr->ia_size & ~PAGE_MASK && + !(attr->ia_valid & ATTR_FILE && + attr->ia_file->f_flags & O_DIRECT)) { pgoff_t offset = attr->ia_size & (PAGE_SIZE - 1); diff --git a/lustre/llite/rw26.c b/lustre/llite/rw26.c index 94b2436..65024f2 100644 --- a/lustre/llite/rw26.c +++ b/lustre/llite/rw26.c @@ -308,6 +308,7 @@ ll_direct_rw_pages(const struct lu_env *env, struct cl_io *io, size_t size, int io_pages = 0; size_t page_size = cl_page_size(obj); int i; + pgoff_t index = offset >> PAGE_SHIFT; ssize_t rc = 0; ENTRY; @@ -329,6 +330,28 @@ ll_direct_rw_pages(const struct lu_env *env, struct cl_io *io, size_t size, } page->cp_sync_io = anchor; + if (inode && IS_ENCRYPTED(inode)) { + struct page *vmpage = cl_page_vmpage(page); + + /* In case of Direct IO on encrypted file, we need to + * set the correct page index, and add a reference to + * the mapping. This is required by llcrypt to proceed + * to encryption/decryption, because each block is + * encrypted independently, and each block's IV is set + * to the logical block number within the file. + * This is safe because we know these pages are private + * to the thread doing the Direct IO, and despite + * setting a mapping on the pages, cached lookups will + * not find them. + * Set PageChecked to detect special case of Direct IO + * in osc_brw_fini_request(). + * Reference to the mapping and PageChecked flag are + * removed in cl_aio_end(). + */ + vmpage->index = index++; + vmpage->mapping = inode->i_mapping; + SetPageChecked(vmpage); + } cl_2queue_add(queue, page); /* * Set page clip to tell transfer formation engine @@ -405,10 +428,6 @@ ll_direct_IO_impl(struct kiocb *iocb, struct iov_iter *iter, int rw) loff_t file_offset = iocb->ki_pos; struct vvp_io *vio; - /* if file is encrypted, return 0 so that we fall back to buffered IO */ - if (IS_ENCRYPTED(inode)) - return 0; - /* Check EOF by ourselves */ if (rw == READ && file_offset >= i_size_read(inode)) return 0; diff --git a/lustre/obdclass/cl_io.c b/lustre/obdclass/cl_io.c index 9c1a553..705aa89 100644 --- a/lustre/obdclass/cl_io.c +++ b/lustre/obdclass/cl_io.c @@ -45,6 +45,7 @@ #include #include #include "cl_internal.h" +#include /***************************************************************************** * @@ -1166,8 +1167,19 @@ static void cl_aio_end(const struct lu_env *env, struct cl_sync_io *anchor) /* release pages */ while (aio->cda_pages.pl_nr > 0) { struct cl_page *page = cl_page_list_first(&aio->cda_pages); + struct page *vmpage = cl_page_vmpage(page); + struct inode *inode = vmpage ? page2inode(vmpage) : NULL; cl_page_get(page); + /* We end up here in case of Direct IO only. For encrypted file, + * mapping was set on pages in ll_direct_rw_pages(), so it has + * to be cleared now before page cleanup. + * PageChecked flag was also set there, so we clean up here. + */ + if (inode && IS_ENCRYPTED(inode)) { + vmpage->mapping = NULL; + ClearPageChecked(vmpage); + } cl_page_list_del(env, &aio->cda_pages, page); cl_page_delete(env, page); cl_page_put(env, page); diff --git a/lustre/osc/osc_request.c b/lustre/osc/osc_request.c index bef2b26..c9db263 100644 --- a/lustre/osc/osc_request.c +++ b/lustre/osc/osc_request.c @@ -1354,13 +1354,9 @@ static inline void osc_release_bounce_pages(struct brw_page **pga, int i; for (i = 0; i < page_count; i++) { - if (pga[i]->pg->mapping) + if (!pga[i]->pg->mapping) /* bounce pages are unmapped */ - continue; - if (pga[i]->flag & OBD_BRW_SYNC) - /* sync transfer cannot have encrypted pages */ - continue; - llcrypt_finalize_bounce_page(&pga[i]->pg); + llcrypt_finalize_bounce_page(&pga[i]->pg); pga[i]->count -= pga[i]->bp_count_diff; pga[i]->off += pga[i]->bp_off_diff; } @@ -1454,6 +1450,19 @@ retry_encrypt: pg->bp_off_diff = pg->off & ~PAGE_MASK; pg->off = pg->off & PAGE_MASK; } + } else if (opc == OST_READ && inode && IS_ENCRYPTED(inode)) { + for (i = 0; i < page_count; i++) { + struct brw_page *pg = pga[i]; + + /* count/off are forced to cover the whole page so that + * all encrypted data is stored on the OST, so adjust + * bp_{count,off}_diff for the size of the clear text. + */ + pg->bp_count_diff = PAGE_SIZE - pg->count; + pg->count = PAGE_SIZE; + pg->bp_off_diff = pg->off & ~PAGE_MASK; + pg->off = pg->off & PAGE_MASK; + } } for (niocount = i = 1; i < page_count; i++) { @@ -1467,8 +1476,13 @@ retry_encrypt: req_capsule_set_size(pill, &RMF_NIOBUF_REMOTE, RCL_CLIENT, niocount * sizeof(*niobuf)); - for (i = 0; i < page_count; i++) + for (i = 0; i < page_count; i++) { short_io_size += pga[i]->count; + if (!inode || !IS_ENCRYPTED(inode)) { + pga[i]->bp_count_diff = 0; + pga[i]->bp_off_diff = 0; + } + } /* Check if read/write is small enough to be a short io. */ if (short_io_size > cli->cl_max_short_io_bytes || niocount > 1 || @@ -2067,8 +2081,17 @@ static int osc_brw_fini_request(struct ptlrpc_request *req, int rc) continue; } + /* The page is already locked when we arrive here, + * except when we deal with a twisted page for + * specific Direct IO support, in which case + * PageChecked flag is set on page. + */ + if (PageChecked(pg->pg)) + lock_page(pg->pg); rc = llcrypt_decrypt_pagecache_blocks(pg->pg, PAGE_SIZE, 0); + if (PageChecked(pg->pg)) + unlock_page(pg->pg); if (rc) GOTO(out, rc); } diff --git a/lustre/tests/sanity-sec.sh b/lustre/tests/sanity-sec.sh index 772a0a0..f8ca92e 100755 --- a/lustre/tests/sanity-sec.sh +++ b/lustre/tests/sanity-sec.sh @@ -3195,6 +3195,9 @@ run_test 43 "test race on encrypted file size (3)" test_44() { local testfile=$DIR/$tdir/$tfile + local tmpfile=$TMP/abc + local resfile=$TMP/resfile + local respage $LCTL get_param mdc.*.import | grep -q client_encryption || skip "client encryption not supported" @@ -3204,28 +3207,59 @@ test_44() { which vmtouch || skip "This test needs vmtouch utility" - # Direct I/O is not supported on encrypted files. - # Attempts to use direct I/O on such files should fall back to - # buffered I/O. + # Direct I/O is now supported on encrypted files. stack_trap cleanup_for_enc_tests EXIT setup_for_enc_tests - # write a page in file with O_DIRECT $LFS setstripe -c1 -i0 $testfile - dd if=/dev/urandom of=$testfile bs=4096 count=1 conv=fsync oflag=direct + dd if=/dev/urandom of=$tmpfile bs=8192 count=1 conv=fsync + dd if=$tmpfile of=$testfile bs=8192 count=1 oflag=direct || + error "could not write to file with O_DIRECT (1)" respage=$(vmtouch $testfile | awk '/Resident\ Pages:/ {print $3}') - [ "$respage" == "1/1" ] || - error "write to enc file did not fall back to buffered IO" + [ "$respage" == "0/2" ] || + error "write to enc file fell back to buffered IO" - cancel_lru_locks osc ; cancel_lru_locks mdc + cancel_lru_locks - dd if=$testfile of=/dev/null bs=4096 count=1 iflag=direct + dd if=$testfile of=$resfile bs=8192 count=1 iflag=direct || + error "could not read from file with O_DIRECT (1)" respage=$(vmtouch $testfile | awk '/Resident\ Pages:/ {print $3}') - [ "$respage" == "1/1" ] || - error "write to enc file did not fall back to buffered IO" + [ "$respage" == "0/2" ] || + error "read from enc file fell back to buffered IO" + + cmp -bl $tmpfile $resfile || + error "file $testfile is corrupted (1)" + + rm -f $resfile + + $TRUNCATE $tmpfile 4096 + dd if=$tmpfile of=$testfile bs=4096 count=1 seek=13 oflag=direct || + error "could not write to file with O_DIRECT (2)" + + cancel_lru_locks + + dd if=$testfile of=$resfile bs=4096 count=1 skip=13 iflag=direct || + error "could not read from file with O_DIRECT (2)" + cmp -bl $tmpfile $resfile || + error "file $testfile is corrupted (2)" + + rm -f $testfile $resfile + $LFS setstripe -c1 -i0 $testfile + + $TRUNCATE $tmpfile 2043 + cp $tmpfile $testfile + + cancel_lru_locks + + dd if=$testfile of=$resfile bs=4096 count=1 iflag=direct || + error "could not read from file with O_DIRECT (3)" + cmp -bl $tmpfile $resfile || + error "file $testfile is corrupted (3)" + + rm -f $tmpfile $resfile } run_test 44 "encrypted file access semantics: direct IO" @@ -3764,6 +3798,62 @@ test_51() { } run_test 51 "FS capabilities ===============" +test_52() { + local testfile=$DIR/$tdir/$tfile + local tmpfile=$TMP/$tfile + local mirror1=$TMP/$tfile.mirror1 + local mirror2=$TMP/$tfile.mirror2 + + $LCTL get_param mdc.*.import | grep -q client_encryption || + skip "client encryption not supported" + + mount.lustre --help |& grep -q "test_dummy_encryption:" || + skip "need dummy encryption support" + + [[ $OSTCOUNT -lt 2 ]] && skip_env "needs >= 2 OSTs" + + stack_trap cleanup_for_enc_tests EXIT + setup_for_enc_tests + + dd if=/dev/urandom of=$tmpfile bs=5000 count=1 conv=fsync + + $LFS mirror create -N -i0 -N -i1 $testfile || + error "could not create mirror" + + dd if=$tmpfile of=$testfile bs=5000 count=1 conv=fsync || + error "could not write to $testfile" + + $LFS mirror resync $testfile || + error "could not resync mirror" + + $LFS mirror verify -v $testfile || + error "verify mirror failed" + + $LFS mirror read -N 1 -o $mirror1 $testfile || + error "could not read from mirror 1" + + cmp -bl $tmpfile $mirror1 || + error "mirror 1 is corrupted" + + $LFS mirror read -N 2 -o $mirror2 $testfile || + error "could not read from mirror 2" + + cmp -bl $tmpfile $mirror2 || + error "mirror 2 is corrupted" + + tr '\0' '2' < /dev/zero | + dd of=$tmpfile bs=9000 count=1 conv=fsync + + $LFS mirror write -N 1 -i $tmpfile $testfile || + error "could not write to mirror 1" + + $LFS mirror verify -v $testfile && + error "mirrors should be different" + + rm -f $tmpfile $mirror1 $mirror2 +} +run_test 52 "Mirrored encrypted file" + log "cleanup: ======================================================" sec_unsetup() { -- 1.8.3.1