Message ID | 1456848492-4814-2-git-send-email-anand.jain@oracle.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Wed, Mar 02, 2016 at 12:08:10AM +0800, Anand Jain wrote: > *** > *** Warning: Experimental code. > *** > > Adds encryption support. The branch is based on v4.5-rc6. > > Signed-off-by: Anand Jain <anand.jain@oracle.com> > --- > fs/btrfs/Makefile | 2 +- > fs/btrfs/btrfs_inode.h | 2 + > fs/btrfs/compression.c | 53 ++++- > fs/btrfs/compression.h | 1 + > fs/btrfs/ctree.h | 11 +- > fs/btrfs/encrypt.c | 544 +++++++++++++++++++++++++++++++++++++++++++++++++ > fs/btrfs/encrypt.h | 21 ++ > fs/btrfs/inode.c | 37 +++- > fs/btrfs/ioctl.c | 7 + > fs/btrfs/props.c | 140 ++++++++++++- > fs/btrfs/super.c | 5 +- > 11 files changed, 812 insertions(+), 11 deletions(-) > create mode 100644 fs/btrfs/encrypt.c > create mode 100644 fs/btrfs/encrypt.h > > diff --git a/fs/btrfs/Makefile b/fs/btrfs/Makefile > index 128ce17a80b0..2765778c5898 100644 > --- a/fs/btrfs/Makefile > +++ b/fs/btrfs/Makefile > @@ -9,7 +9,7 @@ btrfs-y += super.o ctree.o extent-tree.o print-tree.o root-tree.o dir-item.o \ > export.o tree-log.o free-space-cache.o zlib.o lzo.o \ > compression.o delayed-ref.o relocation.o delayed-inode.o scrub.o \ > reada.o backref.o ulist.o qgroup.o send.o dev-replace.o raid56.o \ > - uuid-tree.o props.o hash.o free-space-tree.o > + uuid-tree.o props.o hash.o free-space-tree.o encrypt.o > > btrfs-$(CONFIG_BTRFS_FS_POSIX_ACL) += acl.o > btrfs-$(CONFIG_BTRFS_FS_CHECK_INTEGRITY) += check-integrity.o > diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h > index 61205e3bbefa..4f09572b4922 100644 > --- a/fs/btrfs/btrfs_inode.h > +++ b/fs/btrfs/btrfs_inode.h > @@ -197,6 +197,8 @@ struct btrfs_inode { > long delayed_iput_count; > > struct inode vfs_inode; > + > + unsigned char key_payload[16]; > }; > > extern unsigned char btrfs_filetype_table[]; > diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c > index 3346cd8f9910..d59c366f4200 100644 > --- a/fs/btrfs/compression.c > +++ b/fs/btrfs/compression.c > @@ -41,6 +41,7 @@ > #include "compression.h" > #include "extent_io.h" > #include "extent_map.h" > +#include "encrypt.h" > > struct compressed_bio { > /* number of bios pending for this compressed extent */ > @@ -182,7 +183,7 @@ static void end_compressed_bio_read(struct bio *bio) > cb->orig_bio->bi_vcnt, > cb->compressed_len); > csum_failed: > - if (ret) > + if (ret && ret != -ENOKEY) > cb->errors = 1; > > /* release the compressed pages */ > @@ -751,6 +752,7 @@ static struct { > static const struct btrfs_compress_op * const btrfs_compress_op[] = { > &btrfs_zlib_compress, > &btrfs_lzo_compress, > + &btrfs_encrypt_ops, > }; > > void __init btrfs_init_compress(void) > @@ -780,6 +782,10 @@ static struct list_head *find_workspace(int type) > atomic_t *alloc_ws = &btrfs_comp_ws[idx].alloc_ws; > wait_queue_head_t *ws_wait = &btrfs_comp_ws[idx].ws_wait; > int *num_ws = &btrfs_comp_ws[idx].num_ws; > + > + if (type == BTRFS_ENCRYPT_AES) > + return NULL; > + > again: > spin_lock(ws_lock); > if (!list_empty(idle_ws)) { > @@ -824,6 +830,9 @@ static void free_workspace(int type, struct list_head *workspace) > wait_queue_head_t *ws_wait = &btrfs_comp_ws[idx].ws_wait; > int *num_ws = &btrfs_comp_ws[idx].num_ws; > > + if (!workspace) > + return; > + > spin_lock(ws_lock); > if (*num_ws < num_online_cpus()) { > list_add(workspace, idle_ws); > @@ -862,6 +871,38 @@ static void free_workspaces(void) > } > } > > +void print_data_encode_status(struct inode *inode, int direction, > + char *prefix, int type, int ret) > +{ > +#ifdef CONFIG_BTRFS_DEBUG > + char what[10]; > + > + if (type == BTRFS_ENCRYPT_AES) { > + if (!direction) > + strcpy(what, "Encrypt"); > + else > + strcpy(what, "Decrypt"); > + } else { > + if (!direction) > + strcpy(what, "Compress"); > + else > + strcpy(what, "Uncpress"); > + } > + > + switch (ret) { > + case 0: > + pr_debug("%s %s: success : inode %lu\n",what, prefix, inode->i_ino); > + return; > + case -ENOKEY: > + pr_debug("%s %s: Failed NOKEY: inode %lu\n",what, prefix, inode->i_ino); > + return; > + default: > + pr_debug("%s %s: Failed %d : inode %lu\n",what, prefix, ret, inode->i_ino); > + } > +#else > +#endif > +} > + > /* > * given an address space and start/len, compress the bytes. > * > @@ -894,7 +935,7 @@ int btrfs_compress_pages(int type, struct address_space *mapping, > int ret; > > workspace = find_workspace(type); > - if (IS_ERR(workspace)) > + if (workspace && IS_ERR(workspace)) > return PTR_ERR(workspace); > > ret = btrfs_compress_op[type-1]->compress_pages(workspace, mapping, > @@ -903,6 +944,8 @@ int btrfs_compress_pages(int type, struct address_space *mapping, > total_in, total_out, > max_out); > free_workspace(type, workspace); > + > + print_data_encode_status(mapping->host, 0, " ", type, ret); > return ret; > } > > @@ -930,13 +973,14 @@ static int btrfs_decompress_biovec(int type, struct page **pages_in, > int ret; > > workspace = find_workspace(type); > - if (IS_ERR(workspace)) > + if (workspace && IS_ERR(workspace)) > return PTR_ERR(workspace); > > ret = btrfs_compress_op[type-1]->decompress_biovec(workspace, pages_in, > disk_start, > bvec, vcnt, srclen); > free_workspace(type, workspace); > + print_data_encode_status(bvec->bv_page->mapping->host, 1, "bio ", type, ret); > return ret; > } > > @@ -952,7 +996,7 @@ int btrfs_decompress(int type, unsigned char *data_in, struct page *dest_page, > int ret; > > workspace = find_workspace(type); > - if (IS_ERR(workspace)) > + if (workspace && IS_ERR(workspace)) > return PTR_ERR(workspace); > > ret = btrfs_compress_op[type-1]->decompress(workspace, data_in, > @@ -960,6 +1004,7 @@ int btrfs_decompress(int type, unsigned char *data_in, struct page *dest_page, > srclen, destlen); > > free_workspace(type, workspace); > + print_data_encode_status(dest_page->mapping->host, 1, "page", type, ret); > return ret; > } > > diff --git a/fs/btrfs/compression.h b/fs/btrfs/compression.h > index 13a4dc0436c9..78e8f38dbf60 100644 > --- a/fs/btrfs/compression.h > +++ b/fs/btrfs/compression.h > @@ -79,5 +79,6 @@ struct btrfs_compress_op { > > extern const struct btrfs_compress_op btrfs_zlib_compress; > extern const struct btrfs_compress_op btrfs_lzo_compress; > +extern const struct btrfs_compress_op btrfs_encrypt_ops; > > #endif > diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h > index bfe4a337fb4d..f30a92bf9c54 100644 > --- a/fs/btrfs/ctree.h > +++ b/fs/btrfs/ctree.h > @@ -719,8 +719,9 @@ enum btrfs_compression_type { > BTRFS_COMPRESS_NONE = 0, > BTRFS_COMPRESS_ZLIB = 1, > BTRFS_COMPRESS_LZO = 2, > - BTRFS_COMPRESS_TYPES = 2, > - BTRFS_COMPRESS_LAST = 3, > + BTRFS_ENCRYPT_AES = 3, > + BTRFS_COMPRESS_TYPES = 3, > + BTRFS_COMPRESS_LAST = 4, > }; > > struct btrfs_inode_item { > @@ -771,6 +772,7 @@ struct btrfs_dir_item { > * still visible as a directory > */ > #define BTRFS_ROOT_SUBVOL_DEAD (1ULL << 48) > +#define BTRFS_ROOT_SUBVOL_ENCRYPT (1ULL << 49) > > struct btrfs_root_item { > struct btrfs_inode_item inode; > @@ -814,7 +816,9 @@ struct btrfs_root_item { > struct btrfs_timespec otime; > struct btrfs_timespec stime; > struct btrfs_timespec rtime; > - __le64 reserved[8]; /* for future */ > + char encrypt_algo[16]; > + char encrypt_keytag[16]; > + __le64 reserved[4]; /* for future */ > } __attribute__ ((__packed__)); > > /* > @@ -2344,6 +2348,7 @@ do { \ > #define BTRFS_INODE_NOATIME (1 << 9) > #define BTRFS_INODE_DIRSYNC (1 << 10) > #define BTRFS_INODE_COMPRESS (1 << 11) > +#define BTRFS_INODE_ENCRYPT (1 << 12) > > #define BTRFS_INODE_ROOT_ITEM_INIT (1 << 31) > > diff --git a/fs/btrfs/encrypt.c b/fs/btrfs/encrypt.c > new file mode 100644 > index 000000000000..a6838cccc507 > --- /dev/null > +++ b/fs/btrfs/encrypt.c > @@ -0,0 +1,544 @@ > +/* > + * Copyright (C) 2016 Oracle. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public > + * License v2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public > + * License along with this program; if not, write to the > + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, > + * Boston, MA 021110-1307, USA. > + */ > +#include <linux/string.h> > +#include <linux/crypto.h> > +#include <linux/scatterlist.h> > +#include <linux/random.h> > +#include <linux/pagemap.h> > +#include <keys/user-type.h> > +#include "compression.h" > +#include <linux/slab.h> > +#include <linux/keyctl.h> > +#include <linux/key-type.h> > +#include <linux/cred.h> > +#include <keys/user-type.h> > +#include "ctree.h" > +#include "btrfs_inode.h" > +#include "props.h" > + > +static const struct btrfs_encrypt_algorithm { > + const char *name; > + size_t keylen; > +} btrfs_encrypt_algorithm_supported[] = { > + {"aes", 16} > +}; > + > +/* > + * Returns cipher alg key size if the encryption type is found > + * otherwise 0 > + */ > +size_t btrfs_check_encrypt_type(char *type) > +{ > + int i; > + for (i = 0; i < ARRAY_SIZE(btrfs_encrypt_algorithm_supported); i++) > + if (!strcmp(btrfs_encrypt_algorithm_supported[i].name, type)) > + return btrfs_encrypt_algorithm_supported[i].keylen; > + > + return 0; > +} > + > +/* key management*/ > +static int btrfs_request_key(char *key_tag, void *key_data) > +{ > + int ret; > + const struct user_key_payload *payload; > + struct key *btrfs_key = NULL; > + > + ret = 0; > + btrfs_key = request_key(&key_type_user, key_tag, NULL); > + if (IS_ERR(btrfs_key)) { > + ret = PTR_ERR(btrfs_key); > + btrfs_key = NULL; > + return ret; > + } > + > + /* > + * caller just need key not payload so return > + */ > + if (!key_data) > + return 0; > + > + ret = key_validate(btrfs_key); > + if (ret < 0) > + goto out; > + > + rcu_read_lock(); // TODO: check down_write key->sem ? > + payload = user_key_payload(btrfs_key); > + if (IS_ERR_OR_NULL(payload)) { > + ret = PTR_ERR(payload); > + goto out; > + } > + > + /* > + * As of now we just hard code as we just use ASE now > + */ > + if (payload->datalen != 16) > + ret = -EINVAL; > + else > + memcpy(key_data, payload->data, 16); > + > +out: > + rcu_read_unlock(); > + key_put(btrfs_key); > + > + return ret; > +} > + > +static int btrfs_get_key_data_from_inode(struct inode *inode, unsigned char *keydata) > +{ > + int ret; > + char keytag[15]; > + struct btrfs_inode *binode; > + struct btrfs_root_item *ri; > + > + binode = BTRFS_I(inode); > + ri = &(binode->root->root_item); > + strncpy(keytag, ri->encrypt_keytag, 14); > + keytag[14] = '\0'; > + > + ret = btrfs_request_key(keytag, keydata); > + return ret; > +} > + > +int btrfs_update_key_data_to_binode(struct inode *inode) > +{ > + int ret; > + unsigned char keydata[16]; > + struct btrfs_inode *binode; > + > + ret = btrfs_get_key_data_from_inode(inode, keydata); > + if (ret) > + return ret; > + > + binode = BTRFS_I(inode); > + memcpy(binode->key_payload, keydata, 16); > + > + return ret; > +} > + > +int btrfs_get_keytag(struct address_space *mapping, char *keytag, struct inode **inode) > +{ > + struct btrfs_inode *binode; > + struct btrfs_root_item *ri; > + > + if (!mapping) > + return -EINVAL; > + > + if (!(mapping->host)) > + return -EINVAL; > + > + binode = BTRFS_I(mapping->host); > + ri = &(binode->root->root_item); > + > + strncpy(keytag, ri->encrypt_keytag, 14); > + keytag[14] = '\0'; > + if (inode) > + *inode = &binode->vfs_inode; > + > + return 0; > +} > + > +/* Encrypt and decrypt */ > +struct workspace { > + struct list_head list; > +}; > + > +struct btrfs_crypt_result { > + struct completion completion; > + int err; > +}; > + > +struct btrfs_ablkcipher_def { > + struct scatterlist sg; > + struct crypto_ablkcipher *tfm; > + struct ablkcipher_request *req; > + struct btrfs_crypt_result result; > +}; > + > +int btrfs_do_blkcipher(int enc, char *data, size_t len) > +{ This function is not used anywhere. > + int ret = -EFAULT; > + struct scatterlist sg; > + unsigned int ivsize = 0; > + char *cipher = "cbc(aes)"; > + struct blkcipher_desc desc; > + struct crypto_blkcipher *blkcipher = NULL; > + char *charkey = > + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef"; > + char *chariv = > + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef"; > + > + blkcipher = crypto_alloc_blkcipher(cipher, 0, 0); > + if (IS_ERR(blkcipher)) { > + printk("could not allocate blkcipher handle for %s\n", cipher); > + return -PTR_ERR(blkcipher); > + } > + > + if (crypto_blkcipher_setkey(blkcipher, charkey, 16)) { > + printk("key could not be set\n"); > + ret = -EAGAIN; > + goto out; > + } > + > + ivsize = crypto_blkcipher_ivsize(blkcipher); > + if (ivsize) { > + if (ivsize != strlen(chariv)) { > + printk("length differs from expected length\n"); > + ret = -EINVAL; > + goto out; > + } > + crypto_blkcipher_set_iv(blkcipher, chariv, ivsize); > + } > + > + desc.flags = 0; > + desc.tfm = blkcipher; > + sg_init_one(&sg, data, len); > + > + if (enc) { > + /* encrypt data in place */ > + ret = crypto_blkcipher_encrypt(&desc, &sg, &sg, len); > + } else { > + /* decrypt data in place */ > + ret = crypto_blkcipher_decrypt(&desc, &sg, &sg, len); > + } > + > + return ret; > + > +out: > + crypto_free_blkcipher(blkcipher); > + return ret; > +} > + > +static void btrfs_ablkcipher_cb(struct crypto_async_request *req, int error) > +{ > + struct btrfs_crypt_result *result; > + > + if (error == -EINPROGRESS) { > + pr_info("Encryption callback reports error\n"); > + return; > + } > + > + result = req->data; > + result->err = error; > + complete(&result->completion); > + pr_info("Encryption finished successfully\n"); > +} > + > +static unsigned int btrfs_ablkcipher_encdec(struct btrfs_ablkcipher_def *ablk, int enc) > +{ > + int rc = 0; > + > + if (enc) > + rc = crypto_ablkcipher_encrypt(ablk->req); > + else > + rc = crypto_ablkcipher_decrypt(ablk->req); > + > + switch (rc) { > + case 0: > + break; > + case -EINPROGRESS: > + case -EBUSY: > + rc = wait_for_completion_interruptible( > + &ablk->result.completion); > + if (!rc && !ablk->result.err) { > + reinit_completion(&ablk->result.completion); > + break; > + } > + default: > + pr_info("ablkcipher encrypt returned with %d result %d\n", > + rc, ablk->result.err); > + break; > + } > + init_completion(&ablk->result.completion); > + > + return rc; > +} > + > +void btrfs_cipher_get_ivdata(char **ivdata, unsigned int ivsize, unsigned int *ivdata_size) > +{ > + /* fixme */ > + if (0) { > + *ivdata = kmalloc(ivsize, GFP_KERNEL); > + get_random_bytes(ivdata, ivsize); > + *ivdata_size = ivsize; > + } else { > + *ivdata = kstrdup( > + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef", > + GFP_NOFS); > + *ivdata_size = strlen(*ivdata); > + } > +} > + > +static int btrfs_do_ablkcipher(int endec, struct page *page, unsigned long len, > + struct inode *inode) > +{ > + int ret = -EFAULT; > + unsigned char key_data[16]; > + char *ivdata = NULL; > + unsigned int key_size; > + unsigned int ivsize = 0; > + unsigned int ivdata_size; > + unsigned int ablksize = 0; > + struct btrfs_ablkcipher_def ablk_akin; > + struct ablkcipher_request *req = NULL; > + struct crypto_ablkcipher *ablkcipher = NULL; > + > + ret = 0; > + > + if (!inode) { > + BUG_ON("Need inode\n"); > + return -EINVAL; > + } > + /* get key from the inode */ > + ret = btrfs_get_key_data_from_inode(inode, key_data); > + > + key_size = 16; //todo: defines, but review for suitable cipher > + > + ablkcipher = crypto_alloc_ablkcipher("cts(cbc(aes))", 0, 0); > + if (IS_ERR(ablkcipher)) { > + pr_info("could not allocate ablkcipher handle\n"); > + return PTR_ERR(ablkcipher); > + } > + > + ablksize = crypto_ablkcipher_blocksize(ablkcipher); > + /* we can't cipher a block less the ciper block size */ > + if (len < ablksize || len > PAGE_CACHE_SIZE) { > + ret = -EINVAL; > + goto out; > + } > + > + ivsize = crypto_ablkcipher_ivsize(ablkcipher); > + if (ivsize) { > + btrfs_cipher_get_ivdata(&ivdata, ivsize, &ivdata_size); > + if (ivsize != ivdata_size) { > + BUG_ON("IV length differs from expected length\n"); > + ret = -EINVAL; > + goto out; > + } > + } else { > + BUG_ON("This cipher doesn't need ivdata, but are we ready ?\n"); > + ret = -EINVAL; > + goto out; > + } > + > + req = ablkcipher_request_alloc(ablkcipher, GFP_KERNEL); > + if (IS_ERR(req)) { > + pr_info("could not allocate request queue\n"); > + ret = PTR_ERR(req); > + goto out; > + } > + > + ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, > + btrfs_ablkcipher_cb, &ablk_akin.result); > + > + if (crypto_ablkcipher_setkey(ablkcipher, key_data, key_size)) { > + printk("key could not be set\n"); > + ret = -EAGAIN; > + goto out; > + } > + > + ablk_akin.tfm = ablkcipher; > + ablk_akin.req = req; > + > + sg_init_table(&ablk_akin.sg, 1); > + sg_set_page(&ablk_akin.sg, page, len, 0); > + ablkcipher_request_set_crypt(req, &ablk_akin.sg, &ablk_akin.sg, len, ivdata); Are you sure it is OK to use the same page for src and dst scatterlist? I don't think it's supposed to be used this way. > + > + init_completion(&ablk_akin.result.completion); > + > + ret = btrfs_ablkcipher_encdec(&ablk_akin, endec); > + > +out: > + if (ablkcipher) > + crypto_free_ablkcipher(ablkcipher); req->base.tfm still points to a field in ablkcipher. > + if (req) > + ablkcipher_request_free(req); > + > + kfree(ivdata); > + > + return ret; > +} > + > +static int btrfs_encrypt_pages(struct list_head *na_ws, struct address_space *mapping, > + u64 start, unsigned long len, struct page **pages, > + unsigned long nr_pages, unsigned long *na_out_pages, > + unsigned long *na_total_in, unsigned long *na_total_out, > + unsigned long na_max_out) > +{ > + int ret; > + struct page *in_page; > + struct page *out_page; > + char *in; > + char *out; > + unsigned long bytes_left = len; > + unsigned long cur_page_len; > + unsigned long cur_page; > + struct inode *inode; > + > + *na_total_in = 0; > + *na_out_pages = 0; > + > + if (!mapping && !mapping->host) { > + WARN_ON("Need mapped pages\n"); > + return -EINVAL; > + } > + > + inode = mapping->host; > + > + for (cur_page = 0; cur_page < nr_pages; cur_page++) { > + > + WARN_ON(!bytes_left); > + > + in_page = find_get_page(mapping, start >> PAGE_CACHE_SHIFT); > + out_page = alloc_page(GFP_NOFS| __GFP_HIGHMEM); > + cur_page_len = min(bytes_left, PAGE_CACHE_SIZE); > + > + in = kmap(in_page); > + out = kmap(out_page); > + memcpy(out, in, cur_page_len); > + kunmap(out_page); > + kunmap(in_page); > + > + ret = btrfs_do_ablkcipher(1, out_page, cur_page_len, inode); > + if (ret) { > + __free_page(out_page); > + return ret; > + } > + > + pages[cur_page] = out_page; > + *na_out_pages = *na_out_pages + 1; > + *na_total_in = *na_total_in + cur_page_len; > + > + start += cur_page_len; > + bytes_left = bytes_left - cur_page_len; > + } > + > + return ret; > +} > + > +static int btrfs_decrypt_pages(struct list_head *na_ws, unsigned char *in, struct page *out_page, > + unsigned long na_start_byte, size_t in_size, size_t out_size) > +{ > + int ret; > + char *out_addr; > + char keytag[24]; > + struct address_space *mapping; > + struct inode *inode; > + > + if (!out_page) > + return -EINVAL; > + > + if (in_size > PAGE_CACHE_SIZE) > + return -EINVAL; > + > + memset(keytag, '\0', 24); > + > + mapping = out_page->mapping; > + if (!mapping && !mapping->host) { > + WARN_ON("Need mapped pages\n"); > + return -EINVAL; > + } > + > + inode = mapping->host; > + > + out_addr = kmap(out_page); > + memcpy(out_addr, in, in_size); > + kunmap(out_page); > + > + ret = btrfs_do_ablkcipher(0, out_page, in_size, inode); > + > + return ret; > +} > + > +static int btrfs_decrypt_pages_bio(struct list_head *na_ws, struct page **in_pages, > + u64 in_start_offset, struct bio_vec *out_pages_bio, > + int bi_vcnt, size_t in_len) > +{ > + char *in; > + char *out; > + int ret = 0; > + struct page *in_page; > + struct page *out_page; > + unsigned long cur_page_n; > + unsigned long bytes_left; > + unsigned long in_nr_pages; > + unsigned long cur_page_len; > + unsigned long processed_len = 0; > + struct address_space *mapping; > + struct inode *inode; > + > + if (na_ws) > + return -EINVAL; > + > + out_page = out_pages_bio[0].bv_page; > + mapping = out_page->mapping; > + if (!mapping && !mapping->host) { > + WARN_ON("Need mapped page\n"); > + return -EINVAL; > + } > + > + inode = mapping->host; > + > + in_nr_pages = DIV_ROUND_UP(in_len, PAGE_CACHE_SIZE); > + bytes_left = in_len; > + WARN_ON(in_nr_pages != bi_vcnt); > + > + for (cur_page_n = 0; cur_page_n < in_nr_pages; cur_page_n++) { > + WARN_ON(!bytes_left); > + > + in_page = in_pages[cur_page_n]; > + out_page = out_pages_bio[cur_page_n].bv_page; > + > + cur_page_len = min(bytes_left, PAGE_CACHE_SIZE); > + > + in = kmap(in_page); > + out = kmap(out_page); > + memcpy(out, in, cur_page_len); > + kunmap(out_page); > + kunmap(in_page); > + > + ret = btrfs_do_ablkcipher(0, out_page, cur_page_len, inode); > + > + if (ret && ret != -ENOKEY) > + goto error_out; > + > + if (cur_page_len < PAGE_CACHE_SIZE) { > + out = kmap(out_page); > + memset(out + cur_page_len, 0, PAGE_CACHE_SIZE - cur_page_len); > + kunmap(out_page); > + } > + > + bytes_left = bytes_left - cur_page_len; > + processed_len = processed_len + cur_page_len; > + > + //flush_dcache_page(out_page); > + } > + WARN_ON(processed_len != in_len); > + WARN_ON(bytes_left); > + > +error_out: > + return ret; > +} > + > +const struct btrfs_compress_op btrfs_encrypt_ops = { > + .alloc_workspace = NULL, > + .free_workspace = NULL, > + .compress_pages = btrfs_encrypt_pages, > + .decompress_biovec = btrfs_decrypt_pages_bio, > + .decompress = btrfs_decrypt_pages, > +}; > diff --git a/fs/btrfs/encrypt.h b/fs/btrfs/encrypt.h > new file mode 100644 > index 000000000000..36a7067e98b1 > --- /dev/null > +++ b/fs/btrfs/encrypt.h > @@ -0,0 +1,21 @@ > +/* > + * Copyright (C) 2016 Oracle. All rights reserved. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public > + * License v2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public > + * License along with this program; if not, write to the > + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, > + * Boston, MA 021110-1307, USA. > + */ > + > + > +size_t btrfs_check_encrypt_type(char *encryption_type); > +int btrfs_update_key_data_to_binode(struct inode *inode); > diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c > index 151b7c71b868..b27a89d89753 100644 > --- a/fs/btrfs/inode.c > +++ b/fs/btrfs/inode.c > @@ -60,6 +60,7 @@ > #include "hash.h" > #include "props.h" > #include "qgroup.h" > +#include "encrypt.h" > > struct btrfs_iget_args { > struct btrfs_key *location; > @@ -206,6 +207,8 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans, > } > btrfs_set_file_extent_compression(leaf, ei, > compress_type); > + if (compress_type == BTRFS_ENCRYPT_AES) > + btrfs_set_file_extent_encryption(leaf, ei, 1); Looks like decrypt is not yet used in read_page path, I only see btrfs_set_file_extent_encryption. > } else { > page = find_get_page(inode->i_mapping, > start >> PAGE_CACHE_SHIFT); > @@ -581,7 +584,7 @@ cont: > * win, compare the page count read with the blocks on disk > */ > total_in = ALIGN(total_in, PAGE_CACHE_SIZE); > - if (total_compressed >= total_in) { > + if (total_compressed >= total_in && compress_type != BTRFS_ENCRYPT_AES) { > will_compress = 0; > } else { > num_bytes = total_in; > @@ -6704,6 +6707,8 @@ static noinline int uncompress_inline(struct btrfs_path *path, > max_size = min_t(unsigned long, PAGE_CACHE_SIZE, max_size); > ret = btrfs_decompress(compress_type, tmp, page, > extent_offset, inline_size, max_size); > + if (ret && ret == -ENOKEY) > + ret = 0; > kfree(tmp); > return ret; > } > @@ -9271,6 +9276,20 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry, > u64 root_objectid; > int ret; > u64 old_ino = btrfs_ino(old_inode); > + u64 root_flags; > + u64 dest_flags; > + > + /* > + * As of now block an encrypted file/dir to move across > + * subvol which potentially has different key. > + */ > + root_flags = btrfs_root_flags(&root->root_item); > + dest_flags = btrfs_root_flags(&dest->root_item); > + if (root != dest && > + ((root_flags & BTRFS_ROOT_SUBVOL_ENCRYPT) || > + (dest_flags & BTRFS_ROOT_SUBVOL_ENCRYPT))) { > + return -EOPNOTSUPP; > + } > > if (btrfs_ino(new_dir) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) > return -EPERM; > @@ -9931,6 +9950,22 @@ static int btrfs_permission(struct inode *inode, int mask) > if (BTRFS_I(inode)->flags & BTRFS_INODE_READONLY) > return -EACCES; > } > + > + /* > + * Get the required key as we encrypt only file data, this > + * this applies only files as of now. Double 'this'. > + */ > + if (S_ISREG(mode)) { > + int ret = 0; > + u64 root_flags; > + root_flags = btrfs_root_flags(&root->root_item); > + if (root_flags & BTRFS_ROOT_SUBVOL_ENCRYPT) { > + ret = btrfs_update_key_data_to_binode(inode); > + if (ret) > + return -ENOKEY; > + } > + } > + > return generic_permission(inode, mask); > } > > diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c > index 48aee9846329..3a0f40e4a713 100644 > --- a/fs/btrfs/ioctl.c > +++ b/fs/btrfs/ioctl.c > @@ -2139,8 +2139,15 @@ static noinline int btrfs_ioctl_tree_search(struct file *file, > int ret; > size_t buf_size; > > +#if 0 > + /* > + * Todo: Workaround as of now instead of introduing a new ioctl, > + * so that non root user can find info about the subvol > + * they own/create. This must be fixed in final. > + */ > if (!capable(CAP_SYS_ADMIN)) > return -EPERM; > +#endif > > uargs = (struct btrfs_ioctl_search_args __user *)argp; > > diff --git a/fs/btrfs/props.c b/fs/btrfs/props.c > index f9e60231f685..d40ace5f5492 100644 > --- a/fs/btrfs/props.c > +++ b/fs/btrfs/props.c > @@ -22,10 +22,16 @@ > #include "hash.h" > #include "transaction.h" > #include "xattr.h" > +#include "encrypt.h" > > #define BTRFS_PROP_HANDLERS_HT_BITS 8 > static DEFINE_HASHTABLE(prop_handlers_ht, BTRFS_PROP_HANDLERS_HT_BITS); > > +#define BTRFS_PROP_INHERIT_NONE (1U << 0) > +#define BTRFS_PROP_INHERIT_FOR_DIR (1U << 1) > +#define BTRFS_PROP_INHERIT_FOR_CLONE (1U << 2) > +#define BTRFS_PROP_INHERIT_FOR_SUBVOL (1U << 3) > + > struct prop_handler { > struct hlist_node node; > const char *xattr_name; > @@ -41,13 +47,28 @@ static int prop_compression_apply(struct inode *inode, > size_t len); > static const char *prop_compression_extract(struct inode *inode); > > +static int prop_encrypt_validate(const char *value, size_t len); > +static int prop_encrypt_apply(struct inode *inode, > + const char *value, size_t len); > +static const char *prop_encrypt_extract(struct inode *inode); > + > static struct prop_handler prop_handlers[] = { > { > .xattr_name = XATTR_BTRFS_PREFIX "compression", > .validate = prop_compression_validate, > .apply = prop_compression_apply, > .extract = prop_compression_extract, > - .inheritable = 1 > + .inheritable = BTRFS_PROP_INHERIT_FOR_DIR| \ > + BTRFS_PROP_INHERIT_FOR_CLONE| \ > + BTRFS_PROP_INHERIT_FOR_SUBVOL, > + }, > + { > + .xattr_name = XATTR_BTRFS_PREFIX "encrypt", > + .validate = prop_encrypt_validate, > + .apply = prop_encrypt_apply, > + .extract = prop_encrypt_extract, > + .inheritable = BTRFS_PROP_INHERIT_FOR_DIR| \ > + BTRFS_PROP_INHERIT_FOR_CLONE, > }, > }; > > @@ -315,6 +336,13 @@ static int inherit_props(struct btrfs_trans_handle *trans, > if (!h->inheritable) > continue; > > + //is_subvolume_inode(); ? > + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { > + if (!strcmp(h->xattr_name, "btrfs.encrypt")) { > + continue; > + } > + } > + > value = h->extract(parent); > if (!value) > continue; > @@ -425,4 +453,114 @@ static const char *prop_compression_extract(struct inode *inode) > return NULL; > } > > +static int btrfs_create_encrypt_key_tuplet(char *algo, char *tag, char *val_out) > +{ > + return snprintf(val_out, 32, "%s@%s", algo, tag); > +} > + > +static int btrfs_split_key_tuplet(const char *val, size_t len, > + char *keyalgo, char *keytag) > +{ > + char *tmp; > + char *tmp1; > + char *tmp2; > + > + tmp1 = tmp = kstrdup(val, GFP_NOFS); > + tmp[len] = '\0'; > + tmp2 = strsep(&tmp, "@"); > + if (!tmp2) { > + kfree(tmp1); > + return -EINVAL; > + } > + > + if (strlen(tmp2) > 16 || strlen(tmp) > 16) { > + kfree(tmp1); tmp2 needs to be freed too. > + return -EINVAL; > + } > + strcpy(keyalgo, tmp2); > + strcpy(keytag, tmp); tmp1 and tmp2 need to be freed. > + > + return 0; > +} > + > +/* > + * The required foramt in the value is <encrypt_algo>@<key_tag> > + * eg: btrfs.encrypt="aes@btrfs:61e0d004" > + */ > +static int prop_encrypt_validate(const char *value, size_t len) > +{ > + int ret; > + char keytag[16]; > + char keyalgo[16]; > + size_t keylen; > + > + if (!len) > + return 0; > + > + ret = btrfs_split_key_tuplet(value, len, keyalgo, keytag); > + if (ret) > + return ret; > > + keylen = btrfs_check_encrypt_type(keyalgo); > + if (!keylen) > + return -ENOTSUPP; > + > + return ret; > +} > + > +static int prop_encrypt_apply(struct inode *inode, > + const char *value, size_t len) > +{ > + int ret; > + u64 root_flags; > + char keytag[16]; > + char keyalgo[16]; > + struct btrfs_root_item *root_item; > + > + root_item = &(BTRFS_I(inode)->root->root_item); > + > + if (len == 0) { > + BTRFS_I(inode)->flags &= ~BTRFS_INODE_ENCRYPT; > + BTRFS_I(inode)->force_compress = 0; > + > + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { > + root_flags = btrfs_root_flags(root_item); > + btrfs_set_root_flags(root_item, root_flags | ~BTRFS_ROOT_SUBVOL_ENCRYPT); > + memset(root_item->encrypt_algo, '\0', 16); > + memset(root_item->encrypt_keytag, '\0', 16); > + } > + return 0; > + } > + > + BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT; > + BTRFS_I(inode)->force_compress = BTRFS_ENCRYPT_AES; > + > + ret = btrfs_split_key_tuplet(value, len, keyalgo, keytag); > + if (ret) > + return ret; > + > + /* do it only for the subvol or snapshot */ > + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { > + root_flags = btrfs_root_flags(root_item); > + btrfs_set_root_flags(root_item, root_flags | BTRFS_ROOT_SUBVOL_ENCRYPT); > + /* TODO: this is not right, fix it */ > + strncpy(root_item->encrypt_algo, keyalgo, 16); > + strncpy(root_item->encrypt_keytag, keytag, 16); > + } > + > + return 0; > +} > + > +static const char *prop_encrypt_extract(struct inode *inode) > +{ > + int ret; > + char val[32]; > + struct btrfs_root_item *ri; > + > + ri = &(BTRFS_I(inode)->root->root_item); > + > + ret = btrfs_create_encrypt_key_tuplet(ri->encrypt_algo, > + ri->encrypt_keytag, val); > + > + return kstrdup(val, GFP_NOFS); A pointer is returned here, but in value = h->extract(); value is not be freed by the caller. Thanks, -liubo > +} > diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c > index d41e09fe8e38..400225890f5f 100644 > --- a/fs/btrfs/super.c > +++ b/fs/btrfs/super.c > @@ -59,10 +59,10 @@ > #include "free-space-cache.h" > #include "backref.h" > #include "tests/btrfs-tests.h" > - > #include "qgroup.h" > #define CREATE_TRACE_POINTS > #include <trace/events/btrfs.h> > +#include "encrypt.h" > > static const struct super_operations btrfs_super_ops; > static struct file_system_type btrfs_fs_type; > @@ -92,6 +92,9 @@ const char *btrfs_decode_error(int errno) > case -ENOENT: > errstr = "No such entry"; > break; > + case -ENOKEY: > + errstr = "Required key not available"; > + break; > } > > return errstr; > -- > 2.7.0 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Thanks for the review comments Liu bo. I am looking into the comments. Anand On 03/10/2016 10:19 AM, Liu Bo wrote: > On Wed, Mar 02, 2016 at 12:08:10AM +0800, Anand Jain wrote: >> *** >> *** Warning: Experimental code. >> *** >> >> Adds encryption support. The branch is based on v4.5-rc6. >> >> Signed-off-by: Anand Jain <anand.jain@oracle.com> >> --- >> fs/btrfs/Makefile | 2 +- >> fs/btrfs/btrfs_inode.h | 2 + >> fs/btrfs/compression.c | 53 ++++- >> fs/btrfs/compression.h | 1 + >> fs/btrfs/ctree.h | 11 +- >> fs/btrfs/encrypt.c | 544 +++++++++++++++++++++++++++++++++++++++++++++++++ >> fs/btrfs/encrypt.h | 21 ++ >> fs/btrfs/inode.c | 37 +++- >> fs/btrfs/ioctl.c | 7 + >> fs/btrfs/props.c | 140 ++++++++++++- >> fs/btrfs/super.c | 5 +- >> 11 files changed, 812 insertions(+), 11 deletions(-) >> create mode 100644 fs/btrfs/encrypt.c >> create mode 100644 fs/btrfs/encrypt.h >> >> diff --git a/fs/btrfs/Makefile b/fs/btrfs/Makefile >> index 128ce17a80b0..2765778c5898 100644 >> --- a/fs/btrfs/Makefile >> +++ b/fs/btrfs/Makefile >> @@ -9,7 +9,7 @@ btrfs-y += super.o ctree.o extent-tree.o print-tree.o root-tree.o dir-item.o \ >> export.o tree-log.o free-space-cache.o zlib.o lzo.o \ >> compression.o delayed-ref.o relocation.o delayed-inode.o scrub.o \ >> reada.o backref.o ulist.o qgroup.o send.o dev-replace.o raid56.o \ >> - uuid-tree.o props.o hash.o free-space-tree.o >> + uuid-tree.o props.o hash.o free-space-tree.o encrypt.o >> >> btrfs-$(CONFIG_BTRFS_FS_POSIX_ACL) += acl.o >> btrfs-$(CONFIG_BTRFS_FS_CHECK_INTEGRITY) += check-integrity.o >> diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h >> index 61205e3bbefa..4f09572b4922 100644 >> --- a/fs/btrfs/btrfs_inode.h >> +++ b/fs/btrfs/btrfs_inode.h >> @@ -197,6 +197,8 @@ struct btrfs_inode { >> long delayed_iput_count; >> >> struct inode vfs_inode; >> + >> + unsigned char key_payload[16]; >> }; >> >> extern unsigned char btrfs_filetype_table[]; >> diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c >> index 3346cd8f9910..d59c366f4200 100644 >> --- a/fs/btrfs/compression.c >> +++ b/fs/btrfs/compression.c >> @@ -41,6 +41,7 @@ >> #include "compression.h" >> #include "extent_io.h" >> #include "extent_map.h" >> +#include "encrypt.h" >> >> struct compressed_bio { >> /* number of bios pending for this compressed extent */ >> @@ -182,7 +183,7 @@ static void end_compressed_bio_read(struct bio *bio) >> cb->orig_bio->bi_vcnt, >> cb->compressed_len); >> csum_failed: >> - if (ret) >> + if (ret && ret != -ENOKEY) >> cb->errors = 1; >> >> /* release the compressed pages */ >> @@ -751,6 +752,7 @@ static struct { >> static const struct btrfs_compress_op * const btrfs_compress_op[] = { >> &btrfs_zlib_compress, >> &btrfs_lzo_compress, >> + &btrfs_encrypt_ops, >> }; >> >> void __init btrfs_init_compress(void) >> @@ -780,6 +782,10 @@ static struct list_head *find_workspace(int type) >> atomic_t *alloc_ws = &btrfs_comp_ws[idx].alloc_ws; >> wait_queue_head_t *ws_wait = &btrfs_comp_ws[idx].ws_wait; >> int *num_ws = &btrfs_comp_ws[idx].num_ws; >> + >> + if (type == BTRFS_ENCRYPT_AES) >> + return NULL; >> + >> again: >> spin_lock(ws_lock); >> if (!list_empty(idle_ws)) { >> @@ -824,6 +830,9 @@ static void free_workspace(int type, struct list_head *workspace) >> wait_queue_head_t *ws_wait = &btrfs_comp_ws[idx].ws_wait; >> int *num_ws = &btrfs_comp_ws[idx].num_ws; >> >> + if (!workspace) >> + return; >> + >> spin_lock(ws_lock); >> if (*num_ws < num_online_cpus()) { >> list_add(workspace, idle_ws); >> @@ -862,6 +871,38 @@ static void free_workspaces(void) >> } >> } >> >> +void print_data_encode_status(struct inode *inode, int direction, >> + char *prefix, int type, int ret) >> +{ >> +#ifdef CONFIG_BTRFS_DEBUG >> + char what[10]; >> + >> + if (type == BTRFS_ENCRYPT_AES) { >> + if (!direction) >> + strcpy(what, "Encrypt"); >> + else >> + strcpy(what, "Decrypt"); >> + } else { >> + if (!direction) >> + strcpy(what, "Compress"); >> + else >> + strcpy(what, "Uncpress"); >> + } >> + >> + switch (ret) { >> + case 0: >> + pr_debug("%s %s: success : inode %lu\n",what, prefix, inode->i_ino); >> + return; >> + case -ENOKEY: >> + pr_debug("%s %s: Failed NOKEY: inode %lu\n",what, prefix, inode->i_ino); >> + return; >> + default: >> + pr_debug("%s %s: Failed %d : inode %lu\n",what, prefix, ret, inode->i_ino); >> + } >> +#else >> +#endif >> +} >> + >> /* >> * given an address space and start/len, compress the bytes. >> * >> @@ -894,7 +935,7 @@ int btrfs_compress_pages(int type, struct address_space *mapping, >> int ret; >> >> workspace = find_workspace(type); >> - if (IS_ERR(workspace)) >> + if (workspace && IS_ERR(workspace)) >> return PTR_ERR(workspace); >> >> ret = btrfs_compress_op[type-1]->compress_pages(workspace, mapping, >> @@ -903,6 +944,8 @@ int btrfs_compress_pages(int type, struct address_space *mapping, >> total_in, total_out, >> max_out); >> free_workspace(type, workspace); >> + >> + print_data_encode_status(mapping->host, 0, " ", type, ret); >> return ret; >> } >> >> @@ -930,13 +973,14 @@ static int btrfs_decompress_biovec(int type, struct page **pages_in, >> int ret; >> >> workspace = find_workspace(type); >> - if (IS_ERR(workspace)) >> + if (workspace && IS_ERR(workspace)) >> return PTR_ERR(workspace); >> >> ret = btrfs_compress_op[type-1]->decompress_biovec(workspace, pages_in, >> disk_start, >> bvec, vcnt, srclen); >> free_workspace(type, workspace); >> + print_data_encode_status(bvec->bv_page->mapping->host, 1, "bio ", type, ret); >> return ret; >> } >> >> @@ -952,7 +996,7 @@ int btrfs_decompress(int type, unsigned char *data_in, struct page *dest_page, >> int ret; >> >> workspace = find_workspace(type); >> - if (IS_ERR(workspace)) >> + if (workspace && IS_ERR(workspace)) >> return PTR_ERR(workspace); >> >> ret = btrfs_compress_op[type-1]->decompress(workspace, data_in, >> @@ -960,6 +1004,7 @@ int btrfs_decompress(int type, unsigned char *data_in, struct page *dest_page, >> srclen, destlen); >> >> free_workspace(type, workspace); >> + print_data_encode_status(dest_page->mapping->host, 1, "page", type, ret); >> return ret; >> } >> >> diff --git a/fs/btrfs/compression.h b/fs/btrfs/compression.h >> index 13a4dc0436c9..78e8f38dbf60 100644 >> --- a/fs/btrfs/compression.h >> +++ b/fs/btrfs/compression.h >> @@ -79,5 +79,6 @@ struct btrfs_compress_op { >> >> extern const struct btrfs_compress_op btrfs_zlib_compress; >> extern const struct btrfs_compress_op btrfs_lzo_compress; >> +extern const struct btrfs_compress_op btrfs_encrypt_ops; >> >> #endif >> diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h >> index bfe4a337fb4d..f30a92bf9c54 100644 >> --- a/fs/btrfs/ctree.h >> +++ b/fs/btrfs/ctree.h >> @@ -719,8 +719,9 @@ enum btrfs_compression_type { >> BTRFS_COMPRESS_NONE = 0, >> BTRFS_COMPRESS_ZLIB = 1, >> BTRFS_COMPRESS_LZO = 2, >> - BTRFS_COMPRESS_TYPES = 2, >> - BTRFS_COMPRESS_LAST = 3, >> + BTRFS_ENCRYPT_AES = 3, >> + BTRFS_COMPRESS_TYPES = 3, >> + BTRFS_COMPRESS_LAST = 4, >> }; >> >> struct btrfs_inode_item { >> @@ -771,6 +772,7 @@ struct btrfs_dir_item { >> * still visible as a directory >> */ >> #define BTRFS_ROOT_SUBVOL_DEAD (1ULL << 48) >> +#define BTRFS_ROOT_SUBVOL_ENCRYPT (1ULL << 49) >> >> struct btrfs_root_item { >> struct btrfs_inode_item inode; >> @@ -814,7 +816,9 @@ struct btrfs_root_item { >> struct btrfs_timespec otime; >> struct btrfs_timespec stime; >> struct btrfs_timespec rtime; >> - __le64 reserved[8]; /* for future */ >> + char encrypt_algo[16]; >> + char encrypt_keytag[16]; >> + __le64 reserved[4]; /* for future */ >> } __attribute__ ((__packed__)); >> >> /* >> @@ -2344,6 +2348,7 @@ do { \ >> #define BTRFS_INODE_NOATIME (1 << 9) >> #define BTRFS_INODE_DIRSYNC (1 << 10) >> #define BTRFS_INODE_COMPRESS (1 << 11) >> +#define BTRFS_INODE_ENCRYPT (1 << 12) >> >> #define BTRFS_INODE_ROOT_ITEM_INIT (1 << 31) >> >> diff --git a/fs/btrfs/encrypt.c b/fs/btrfs/encrypt.c >> new file mode 100644 >> index 000000000000..a6838cccc507 >> --- /dev/null >> +++ b/fs/btrfs/encrypt.c >> @@ -0,0 +1,544 @@ >> +/* >> + * Copyright (C) 2016 Oracle. All rights reserved. >> + * >> + * This program is free software; you can redistribute it and/or >> + * modify it under the terms of the GNU General Public >> + * License v2 as published by the Free Software Foundation. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >> + * General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public >> + * License along with this program; if not, write to the >> + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, >> + * Boston, MA 021110-1307, USA. >> + */ >> +#include <linux/string.h> >> +#include <linux/crypto.h> >> +#include <linux/scatterlist.h> >> +#include <linux/random.h> >> +#include <linux/pagemap.h> >> +#include <keys/user-type.h> >> +#include "compression.h" >> +#include <linux/slab.h> >> +#include <linux/keyctl.h> >> +#include <linux/key-type.h> >> +#include <linux/cred.h> >> +#include <keys/user-type.h> >> +#include "ctree.h" >> +#include "btrfs_inode.h" >> +#include "props.h" >> + >> +static const struct btrfs_encrypt_algorithm { >> + const char *name; >> + size_t keylen; >> +} btrfs_encrypt_algorithm_supported[] = { >> + {"aes", 16} >> +}; >> + >> +/* >> + * Returns cipher alg key size if the encryption type is found >> + * otherwise 0 >> + */ >> +size_t btrfs_check_encrypt_type(char *type) >> +{ >> + int i; >> + for (i = 0; i < ARRAY_SIZE(btrfs_encrypt_algorithm_supported); i++) >> + if (!strcmp(btrfs_encrypt_algorithm_supported[i].name, type)) >> + return btrfs_encrypt_algorithm_supported[i].keylen; >> + >> + return 0; >> +} >> + >> +/* key management*/ >> +static int btrfs_request_key(char *key_tag, void *key_data) >> +{ >> + int ret; >> + const struct user_key_payload *payload; >> + struct key *btrfs_key = NULL; >> + >> + ret = 0; >> + btrfs_key = request_key(&key_type_user, key_tag, NULL); >> + if (IS_ERR(btrfs_key)) { >> + ret = PTR_ERR(btrfs_key); >> + btrfs_key = NULL; >> + return ret; >> + } >> + >> + /* >> + * caller just need key not payload so return >> + */ >> + if (!key_data) >> + return 0; >> + >> + ret = key_validate(btrfs_key); >> + if (ret < 0) >> + goto out; >> + >> + rcu_read_lock(); // TODO: check down_write key->sem ? >> + payload = user_key_payload(btrfs_key); >> + if (IS_ERR_OR_NULL(payload)) { >> + ret = PTR_ERR(payload); >> + goto out; >> + } >> + >> + /* >> + * As of now we just hard code as we just use ASE now >> + */ >> + if (payload->datalen != 16) >> + ret = -EINVAL; >> + else >> + memcpy(key_data, payload->data, 16); >> + >> +out: >> + rcu_read_unlock(); >> + key_put(btrfs_key); >> + >> + return ret; >> +} >> + >> +static int btrfs_get_key_data_from_inode(struct inode *inode, unsigned char *keydata) >> +{ >> + int ret; >> + char keytag[15]; >> + struct btrfs_inode *binode; >> + struct btrfs_root_item *ri; >> + >> + binode = BTRFS_I(inode); >> + ri = &(binode->root->root_item); >> + strncpy(keytag, ri->encrypt_keytag, 14); >> + keytag[14] = '\0'; >> + >> + ret = btrfs_request_key(keytag, keydata); >> + return ret; >> +} >> + >> +int btrfs_update_key_data_to_binode(struct inode *inode) >> +{ >> + int ret; >> + unsigned char keydata[16]; >> + struct btrfs_inode *binode; >> + >> + ret = btrfs_get_key_data_from_inode(inode, keydata); >> + if (ret) >> + return ret; >> + >> + binode = BTRFS_I(inode); >> + memcpy(binode->key_payload, keydata, 16); >> + >> + return ret; >> +} >> + >> +int btrfs_get_keytag(struct address_space *mapping, char *keytag, struct inode **inode) >> +{ >> + struct btrfs_inode *binode; >> + struct btrfs_root_item *ri; >> + >> + if (!mapping) >> + return -EINVAL; >> + >> + if (!(mapping->host)) >> + return -EINVAL; >> + >> + binode = BTRFS_I(mapping->host); >> + ri = &(binode->root->root_item); >> + >> + strncpy(keytag, ri->encrypt_keytag, 14); >> + keytag[14] = '\0'; >> + if (inode) >> + *inode = &binode->vfs_inode; >> + >> + return 0; >> +} >> + >> +/* Encrypt and decrypt */ >> +struct workspace { >> + struct list_head list; >> +}; >> + >> +struct btrfs_crypt_result { >> + struct completion completion; >> + int err; >> +}; >> + >> +struct btrfs_ablkcipher_def { >> + struct scatterlist sg; >> + struct crypto_ablkcipher *tfm; >> + struct ablkcipher_request *req; >> + struct btrfs_crypt_result result; >> +}; >> + >> +int btrfs_do_blkcipher(int enc, char *data, size_t len) >> +{ > > This function is not used anywhere. > >> + int ret = -EFAULT; >> + struct scatterlist sg; >> + unsigned int ivsize = 0; >> + char *cipher = "cbc(aes)"; >> + struct blkcipher_desc desc; >> + struct crypto_blkcipher *blkcipher = NULL; >> + char *charkey = >> + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef"; >> + char *chariv = >> + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef"; >> + >> + blkcipher = crypto_alloc_blkcipher(cipher, 0, 0); >> + if (IS_ERR(blkcipher)) { >> + printk("could not allocate blkcipher handle for %s\n", cipher); >> + return -PTR_ERR(blkcipher); >> + } >> + >> + if (crypto_blkcipher_setkey(blkcipher, charkey, 16)) { >> + printk("key could not be set\n"); >> + ret = -EAGAIN; >> + goto out; >> + } >> + >> + ivsize = crypto_blkcipher_ivsize(blkcipher); >> + if (ivsize) { >> + if (ivsize != strlen(chariv)) { >> + printk("length differs from expected length\n"); >> + ret = -EINVAL; >> + goto out; >> + } >> + crypto_blkcipher_set_iv(blkcipher, chariv, ivsize); >> + } >> + >> + desc.flags = 0; >> + desc.tfm = blkcipher; >> + sg_init_one(&sg, data, len); >> + >> + if (enc) { >> + /* encrypt data in place */ >> + ret = crypto_blkcipher_encrypt(&desc, &sg, &sg, len); >> + } else { >> + /* decrypt data in place */ >> + ret = crypto_blkcipher_decrypt(&desc, &sg, &sg, len); >> + } >> + >> + return ret; >> + >> +out: >> + crypto_free_blkcipher(blkcipher); >> + return ret; >> +} >> + >> +static void btrfs_ablkcipher_cb(struct crypto_async_request *req, int error) >> +{ >> + struct btrfs_crypt_result *result; >> + >> + if (error == -EINPROGRESS) { >> + pr_info("Encryption callback reports error\n"); >> + return; >> + } >> + >> + result = req->data; >> + result->err = error; >> + complete(&result->completion); >> + pr_info("Encryption finished successfully\n"); >> +} >> + >> +static unsigned int btrfs_ablkcipher_encdec(struct btrfs_ablkcipher_def *ablk, int enc) >> +{ >> + int rc = 0; >> + >> + if (enc) >> + rc = crypto_ablkcipher_encrypt(ablk->req); >> + else >> + rc = crypto_ablkcipher_decrypt(ablk->req); >> + >> + switch (rc) { >> + case 0: >> + break; >> + case -EINPROGRESS: >> + case -EBUSY: >> + rc = wait_for_completion_interruptible( >> + &ablk->result.completion); >> + if (!rc && !ablk->result.err) { >> + reinit_completion(&ablk->result.completion); >> + break; >> + } >> + default: >> + pr_info("ablkcipher encrypt returned with %d result %d\n", >> + rc, ablk->result.err); >> + break; >> + } >> + init_completion(&ablk->result.completion); >> + >> + return rc; >> +} >> + >> +void btrfs_cipher_get_ivdata(char **ivdata, unsigned int ivsize, unsigned int *ivdata_size) >> +{ >> + /* fixme */ >> + if (0) { >> + *ivdata = kmalloc(ivsize, GFP_KERNEL); >> + get_random_bytes(ivdata, ivsize); >> + *ivdata_size = ivsize; >> + } else { >> + *ivdata = kstrdup( >> + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef", >> + GFP_NOFS); >> + *ivdata_size = strlen(*ivdata); >> + } >> +} >> + >> +static int btrfs_do_ablkcipher(int endec, struct page *page, unsigned long len, >> + struct inode *inode) >> +{ >> + int ret = -EFAULT; >> + unsigned char key_data[16]; >> + char *ivdata = NULL; >> + unsigned int key_size; >> + unsigned int ivsize = 0; >> + unsigned int ivdata_size; >> + unsigned int ablksize = 0; >> + struct btrfs_ablkcipher_def ablk_akin; >> + struct ablkcipher_request *req = NULL; >> + struct crypto_ablkcipher *ablkcipher = NULL; >> + >> + ret = 0; >> + >> + if (!inode) { >> + BUG_ON("Need inode\n"); >> + return -EINVAL; >> + } >> + /* get key from the inode */ >> + ret = btrfs_get_key_data_from_inode(inode, key_data); >> + >> + key_size = 16; //todo: defines, but review for suitable cipher >> + >> + ablkcipher = crypto_alloc_ablkcipher("cts(cbc(aes))", 0, 0); >> + if (IS_ERR(ablkcipher)) { >> + pr_info("could not allocate ablkcipher handle\n"); >> + return PTR_ERR(ablkcipher); >> + } >> + >> + ablksize = crypto_ablkcipher_blocksize(ablkcipher); >> + /* we can't cipher a block less the ciper block size */ >> + if (len < ablksize || len > PAGE_CACHE_SIZE) { >> + ret = -EINVAL; >> + goto out; >> + } >> + >> + ivsize = crypto_ablkcipher_ivsize(ablkcipher); >> + if (ivsize) { >> + btrfs_cipher_get_ivdata(&ivdata, ivsize, &ivdata_size); >> + if (ivsize != ivdata_size) { >> + BUG_ON("IV length differs from expected length\n"); >> + ret = -EINVAL; >> + goto out; >> + } >> + } else { >> + BUG_ON("This cipher doesn't need ivdata, but are we ready ?\n"); >> + ret = -EINVAL; >> + goto out; >> + } >> + >> + req = ablkcipher_request_alloc(ablkcipher, GFP_KERNEL); >> + if (IS_ERR(req)) { >> + pr_info("could not allocate request queue\n"); >> + ret = PTR_ERR(req); >> + goto out; >> + } >> + >> + ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, >> + btrfs_ablkcipher_cb, &ablk_akin.result); >> + >> + if (crypto_ablkcipher_setkey(ablkcipher, key_data, key_size)) { >> + printk("key could not be set\n"); >> + ret = -EAGAIN; >> + goto out; >> + } >> + >> + ablk_akin.tfm = ablkcipher; >> + ablk_akin.req = req; >> + >> + sg_init_table(&ablk_akin.sg, 1); >> + sg_set_page(&ablk_akin.sg, page, len, 0); >> + ablkcipher_request_set_crypt(req, &ablk_akin.sg, &ablk_akin.sg, len, ivdata); > > Are you sure it is OK to use the same page for src and dst scatterlist? > > I don't think it's supposed to be used this way. > >> + >> + init_completion(&ablk_akin.result.completion); >> + >> + ret = btrfs_ablkcipher_encdec(&ablk_akin, endec); >> + >> +out: >> + if (ablkcipher) >> + crypto_free_ablkcipher(ablkcipher); > > req->base.tfm still points to a field in ablkcipher. > >> + if (req) >> + ablkcipher_request_free(req); >> + >> + kfree(ivdata); >> + >> + return ret; >> +} >> + >> +static int btrfs_encrypt_pages(struct list_head *na_ws, struct address_space *mapping, >> + u64 start, unsigned long len, struct page **pages, >> + unsigned long nr_pages, unsigned long *na_out_pages, >> + unsigned long *na_total_in, unsigned long *na_total_out, >> + unsigned long na_max_out) >> +{ >> + int ret; >> + struct page *in_page; >> + struct page *out_page; >> + char *in; >> + char *out; >> + unsigned long bytes_left = len; >> + unsigned long cur_page_len; >> + unsigned long cur_page; >> + struct inode *inode; >> + >> + *na_total_in = 0; >> + *na_out_pages = 0; >> + >> + if (!mapping && !mapping->host) { >> + WARN_ON("Need mapped pages\n"); >> + return -EINVAL; >> + } >> + >> + inode = mapping->host; >> + >> + for (cur_page = 0; cur_page < nr_pages; cur_page++) { >> + >> + WARN_ON(!bytes_left); >> + >> + in_page = find_get_page(mapping, start >> PAGE_CACHE_SHIFT); >> + out_page = alloc_page(GFP_NOFS| __GFP_HIGHMEM); >> + cur_page_len = min(bytes_left, PAGE_CACHE_SIZE); >> + >> + in = kmap(in_page); >> + out = kmap(out_page); >> + memcpy(out, in, cur_page_len); >> + kunmap(out_page); >> + kunmap(in_page); >> + >> + ret = btrfs_do_ablkcipher(1, out_page, cur_page_len, inode); >> + if (ret) { >> + __free_page(out_page); >> + return ret; >> + } >> + >> + pages[cur_page] = out_page; >> + *na_out_pages = *na_out_pages + 1; >> + *na_total_in = *na_total_in + cur_page_len; >> + >> + start += cur_page_len; >> + bytes_left = bytes_left - cur_page_len; >> + } >> + >> + return ret; >> +} >> + >> +static int btrfs_decrypt_pages(struct list_head *na_ws, unsigned char *in, struct page *out_page, >> + unsigned long na_start_byte, size_t in_size, size_t out_size) >> +{ >> + int ret; >> + char *out_addr; >> + char keytag[24]; >> + struct address_space *mapping; >> + struct inode *inode; >> + >> + if (!out_page) >> + return -EINVAL; >> + >> + if (in_size > PAGE_CACHE_SIZE) >> + return -EINVAL; >> + >> + memset(keytag, '\0', 24); >> + >> + mapping = out_page->mapping; >> + if (!mapping && !mapping->host) { >> + WARN_ON("Need mapped pages\n"); >> + return -EINVAL; >> + } >> + >> + inode = mapping->host; >> + >> + out_addr = kmap(out_page); >> + memcpy(out_addr, in, in_size); >> + kunmap(out_page); >> + >> + ret = btrfs_do_ablkcipher(0, out_page, in_size, inode); >> + >> + return ret; >> +} >> + >> +static int btrfs_decrypt_pages_bio(struct list_head *na_ws, struct page **in_pages, >> + u64 in_start_offset, struct bio_vec *out_pages_bio, >> + int bi_vcnt, size_t in_len) >> +{ >> + char *in; >> + char *out; >> + int ret = 0; >> + struct page *in_page; >> + struct page *out_page; >> + unsigned long cur_page_n; >> + unsigned long bytes_left; >> + unsigned long in_nr_pages; >> + unsigned long cur_page_len; >> + unsigned long processed_len = 0; >> + struct address_space *mapping; >> + struct inode *inode; >> + >> + if (na_ws) >> + return -EINVAL; >> + >> + out_page = out_pages_bio[0].bv_page; >> + mapping = out_page->mapping; >> + if (!mapping && !mapping->host) { >> + WARN_ON("Need mapped page\n"); >> + return -EINVAL; >> + } >> + >> + inode = mapping->host; >> + >> + in_nr_pages = DIV_ROUND_UP(in_len, PAGE_CACHE_SIZE); >> + bytes_left = in_len; >> + WARN_ON(in_nr_pages != bi_vcnt); >> + >> + for (cur_page_n = 0; cur_page_n < in_nr_pages; cur_page_n++) { >> + WARN_ON(!bytes_left); >> + >> + in_page = in_pages[cur_page_n]; >> + out_page = out_pages_bio[cur_page_n].bv_page; >> + >> + cur_page_len = min(bytes_left, PAGE_CACHE_SIZE); >> + >> + in = kmap(in_page); >> + out = kmap(out_page); >> + memcpy(out, in, cur_page_len); >> + kunmap(out_page); >> + kunmap(in_page); >> + >> + ret = btrfs_do_ablkcipher(0, out_page, cur_page_len, inode); >> + >> + if (ret && ret != -ENOKEY) >> + goto error_out; >> + >> + if (cur_page_len < PAGE_CACHE_SIZE) { >> + out = kmap(out_page); >> + memset(out + cur_page_len, 0, PAGE_CACHE_SIZE - cur_page_len); >> + kunmap(out_page); >> + } >> + >> + bytes_left = bytes_left - cur_page_len; >> + processed_len = processed_len + cur_page_len; >> + >> + //flush_dcache_page(out_page); >> + } >> + WARN_ON(processed_len != in_len); >> + WARN_ON(bytes_left); >> + >> +error_out: >> + return ret; >> +} >> + >> +const struct btrfs_compress_op btrfs_encrypt_ops = { >> + .alloc_workspace = NULL, >> + .free_workspace = NULL, >> + .compress_pages = btrfs_encrypt_pages, >> + .decompress_biovec = btrfs_decrypt_pages_bio, >> + .decompress = btrfs_decrypt_pages, >> +}; >> diff --git a/fs/btrfs/encrypt.h b/fs/btrfs/encrypt.h >> new file mode 100644 >> index 000000000000..36a7067e98b1 >> --- /dev/null >> +++ b/fs/btrfs/encrypt.h >> @@ -0,0 +1,21 @@ >> +/* >> + * Copyright (C) 2016 Oracle. All rights reserved. >> + * >> + * This program is free software; you can redistribute it and/or >> + * modify it under the terms of the GNU General Public >> + * License v2 as published by the Free Software Foundation. >> + * >> + * This program is distributed in the hope that it will be useful, >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU >> + * General Public License for more details. >> + * >> + * You should have received a copy of the GNU General Public >> + * License along with this program; if not, write to the >> + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, >> + * Boston, MA 021110-1307, USA. >> + */ >> + >> + >> +size_t btrfs_check_encrypt_type(char *encryption_type); >> +int btrfs_update_key_data_to_binode(struct inode *inode); >> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c >> index 151b7c71b868..b27a89d89753 100644 >> --- a/fs/btrfs/inode.c >> +++ b/fs/btrfs/inode.c >> @@ -60,6 +60,7 @@ >> #include "hash.h" >> #include "props.h" >> #include "qgroup.h" >> +#include "encrypt.h" >> >> struct btrfs_iget_args { >> struct btrfs_key *location; >> @@ -206,6 +207,8 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans, >> } >> btrfs_set_file_extent_compression(leaf, ei, >> compress_type); >> + if (compress_type == BTRFS_ENCRYPT_AES) >> + btrfs_set_file_extent_encryption(leaf, ei, 1); > > Looks like decrypt is not yet used in read_page path, I only see btrfs_set_file_extent_encryption. > >> } else { >> page = find_get_page(inode->i_mapping, >> start >> PAGE_CACHE_SHIFT); >> @@ -581,7 +584,7 @@ cont: >> * win, compare the page count read with the blocks on disk >> */ >> total_in = ALIGN(total_in, PAGE_CACHE_SIZE); >> - if (total_compressed >= total_in) { >> + if (total_compressed >= total_in && compress_type != BTRFS_ENCRYPT_AES) { >> will_compress = 0; >> } else { >> num_bytes = total_in; >> @@ -6704,6 +6707,8 @@ static noinline int uncompress_inline(struct btrfs_path *path, >> max_size = min_t(unsigned long, PAGE_CACHE_SIZE, max_size); >> ret = btrfs_decompress(compress_type, tmp, page, >> extent_offset, inline_size, max_size); >> + if (ret && ret == -ENOKEY) >> + ret = 0; >> kfree(tmp); >> return ret; >> } >> @@ -9271,6 +9276,20 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry, >> u64 root_objectid; >> int ret; >> u64 old_ino = btrfs_ino(old_inode); >> + u64 root_flags; >> + u64 dest_flags; >> + >> + /* >> + * As of now block an encrypted file/dir to move across >> + * subvol which potentially has different key. >> + */ >> + root_flags = btrfs_root_flags(&root->root_item); >> + dest_flags = btrfs_root_flags(&dest->root_item); >> + if (root != dest && >> + ((root_flags & BTRFS_ROOT_SUBVOL_ENCRYPT) || >> + (dest_flags & BTRFS_ROOT_SUBVOL_ENCRYPT))) { >> + return -EOPNOTSUPP; >> + } >> >> if (btrfs_ino(new_dir) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) >> return -EPERM; >> @@ -9931,6 +9950,22 @@ static int btrfs_permission(struct inode *inode, int mask) >> if (BTRFS_I(inode)->flags & BTRFS_INODE_READONLY) >> return -EACCES; >> } >> + >> + /* >> + * Get the required key as we encrypt only file data, this >> + * this applies only files as of now. > > Double 'this'. > >> + */ >> + if (S_ISREG(mode)) { >> + int ret = 0; >> + u64 root_flags; >> + root_flags = btrfs_root_flags(&root->root_item); >> + if (root_flags & BTRFS_ROOT_SUBVOL_ENCRYPT) { >> + ret = btrfs_update_key_data_to_binode(inode); >> + if (ret) >> + return -ENOKEY; >> + } >> + } >> + >> return generic_permission(inode, mask); >> } >> >> diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c >> index 48aee9846329..3a0f40e4a713 100644 >> --- a/fs/btrfs/ioctl.c >> +++ b/fs/btrfs/ioctl.c >> @@ -2139,8 +2139,15 @@ static noinline int btrfs_ioctl_tree_search(struct file *file, >> int ret; >> size_t buf_size; >> >> +#if 0 >> + /* >> + * Todo: Workaround as of now instead of introduing a new ioctl, >> + * so that non root user can find info about the subvol >> + * they own/create. This must be fixed in final. >> + */ >> if (!capable(CAP_SYS_ADMIN)) >> return -EPERM; >> +#endif >> >> uargs = (struct btrfs_ioctl_search_args __user *)argp; >> >> diff --git a/fs/btrfs/props.c b/fs/btrfs/props.c >> index f9e60231f685..d40ace5f5492 100644 >> --- a/fs/btrfs/props.c >> +++ b/fs/btrfs/props.c >> @@ -22,10 +22,16 @@ >> #include "hash.h" >> #include "transaction.h" >> #include "xattr.h" >> +#include "encrypt.h" >> >> #define BTRFS_PROP_HANDLERS_HT_BITS 8 >> static DEFINE_HASHTABLE(prop_handlers_ht, BTRFS_PROP_HANDLERS_HT_BITS); >> >> +#define BTRFS_PROP_INHERIT_NONE (1U << 0) >> +#define BTRFS_PROP_INHERIT_FOR_DIR (1U << 1) >> +#define BTRFS_PROP_INHERIT_FOR_CLONE (1U << 2) >> +#define BTRFS_PROP_INHERIT_FOR_SUBVOL (1U << 3) >> + >> struct prop_handler { >> struct hlist_node node; >> const char *xattr_name; >> @@ -41,13 +47,28 @@ static int prop_compression_apply(struct inode *inode, >> size_t len); >> static const char *prop_compression_extract(struct inode *inode); >> >> +static int prop_encrypt_validate(const char *value, size_t len); >> +static int prop_encrypt_apply(struct inode *inode, >> + const char *value, size_t len); >> +static const char *prop_encrypt_extract(struct inode *inode); >> + >> static struct prop_handler prop_handlers[] = { >> { >> .xattr_name = XATTR_BTRFS_PREFIX "compression", >> .validate = prop_compression_validate, >> .apply = prop_compression_apply, >> .extract = prop_compression_extract, >> - .inheritable = 1 >> + .inheritable = BTRFS_PROP_INHERIT_FOR_DIR| \ >> + BTRFS_PROP_INHERIT_FOR_CLONE| \ >> + BTRFS_PROP_INHERIT_FOR_SUBVOL, >> + }, >> + { >> + .xattr_name = XATTR_BTRFS_PREFIX "encrypt", >> + .validate = prop_encrypt_validate, >> + .apply = prop_encrypt_apply, >> + .extract = prop_encrypt_extract, >> + .inheritable = BTRFS_PROP_INHERIT_FOR_DIR| \ >> + BTRFS_PROP_INHERIT_FOR_CLONE, >> }, >> }; >> >> @@ -315,6 +336,13 @@ static int inherit_props(struct btrfs_trans_handle *trans, >> if (!h->inheritable) >> continue; >> >> + //is_subvolume_inode(); ? >> + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { >> + if (!strcmp(h->xattr_name, "btrfs.encrypt")) { >> + continue; >> + } >> + } >> + >> value = h->extract(parent); >> if (!value) >> continue; >> @@ -425,4 +453,114 @@ static const char *prop_compression_extract(struct inode *inode) >> return NULL; >> } >> >> +static int btrfs_create_encrypt_key_tuplet(char *algo, char *tag, char *val_out) >> +{ >> + return snprintf(val_out, 32, "%s@%s", algo, tag); >> +} >> + >> +static int btrfs_split_key_tuplet(const char *val, size_t len, >> + char *keyalgo, char *keytag) >> +{ >> + char *tmp; >> + char *tmp1; >> + char *tmp2; >> + >> + tmp1 = tmp = kstrdup(val, GFP_NOFS); >> + tmp[len] = '\0'; >> + tmp2 = strsep(&tmp, "@"); >> + if (!tmp2) { >> + kfree(tmp1); >> + return -EINVAL; >> + } >> + >> + if (strlen(tmp2) > 16 || strlen(tmp) > 16) { >> + kfree(tmp1); > > tmp2 needs to be freed too. > >> + return -EINVAL; >> + } >> + strcpy(keyalgo, tmp2); >> + strcpy(keytag, tmp); > > tmp1 and tmp2 need to be freed. > >> + >> + return 0; >> +} >> + >> +/* >> + * The required foramt in the value is <encrypt_algo>@<key_tag> >> + * eg: btrfs.encrypt="aes@btrfs:61e0d004" >> + */ >> +static int prop_encrypt_validate(const char *value, size_t len) >> +{ >> + int ret; >> + char keytag[16]; >> + char keyalgo[16]; >> + size_t keylen; >> + >> + if (!len) >> + return 0; >> + >> + ret = btrfs_split_key_tuplet(value, len, keyalgo, keytag); >> + if (ret) >> + return ret; >> >> + keylen = btrfs_check_encrypt_type(keyalgo); >> + if (!keylen) >> + return -ENOTSUPP; >> + >> + return ret; >> +} >> + >> +static int prop_encrypt_apply(struct inode *inode, >> + const char *value, size_t len) >> +{ >> + int ret; >> + u64 root_flags; >> + char keytag[16]; >> + char keyalgo[16]; >> + struct btrfs_root_item *root_item; >> + >> + root_item = &(BTRFS_I(inode)->root->root_item); >> + >> + if (len == 0) { >> + BTRFS_I(inode)->flags &= ~BTRFS_INODE_ENCRYPT; >> + BTRFS_I(inode)->force_compress = 0; >> + >> + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { >> + root_flags = btrfs_root_flags(root_item); >> + btrfs_set_root_flags(root_item, root_flags | ~BTRFS_ROOT_SUBVOL_ENCRYPT); >> + memset(root_item->encrypt_algo, '\0', 16); >> + memset(root_item->encrypt_keytag, '\0', 16); >> + } >> + return 0; >> + } >> + >> + BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT; >> + BTRFS_I(inode)->force_compress = BTRFS_ENCRYPT_AES; >> + >> + ret = btrfs_split_key_tuplet(value, len, keyalgo, keytag); >> + if (ret) >> + return ret; >> + >> + /* do it only for the subvol or snapshot */ >> + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { >> + root_flags = btrfs_root_flags(root_item); >> + btrfs_set_root_flags(root_item, root_flags | BTRFS_ROOT_SUBVOL_ENCRYPT); >> + /* TODO: this is not right, fix it */ >> + strncpy(root_item->encrypt_algo, keyalgo, 16); >> + strncpy(root_item->encrypt_keytag, keytag, 16); >> + } >> + >> + return 0; >> +} >> + >> +static const char *prop_encrypt_extract(struct inode *inode) >> +{ >> + int ret; >> + char val[32]; >> + struct btrfs_root_item *ri; >> + >> + ri = &(BTRFS_I(inode)->root->root_item); >> + >> + ret = btrfs_create_encrypt_key_tuplet(ri->encrypt_algo, >> + ri->encrypt_keytag, val); >> + >> + return kstrdup(val, GFP_NOFS); > > A pointer is returned here, but in > > value = h->extract(); > > value is not be freed by the caller. > > Thanks, > > -liubo > >> +} >> diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c >> index d41e09fe8e38..400225890f5f 100644 >> --- a/fs/btrfs/super.c >> +++ b/fs/btrfs/super.c >> @@ -59,10 +59,10 @@ >> #include "free-space-cache.h" >> #include "backref.h" >> #include "tests/btrfs-tests.h" >> - >> #include "qgroup.h" >> #define CREATE_TRACE_POINTS >> #include <trace/events/btrfs.h> >> +#include "encrypt.h" >> >> static const struct super_operations btrfs_super_ops; >> static struct file_system_type btrfs_fs_type; >> @@ -92,6 +92,9 @@ const char *btrfs_decode_error(int errno) >> case -ENOENT: >> errstr = "No such entry"; >> break; >> + case -ENOKEY: >> + errstr = "Required key not available"; >> + break; >> } >> >> return errstr; >> -- >> 2.7.0 >> >> -- >> To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in >> the body of a message to majordomo@vger.kernel.org >> More majordomo info at http://vger.kernel.org/majordomo-info.html > -- > To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html > -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/fs/btrfs/Makefile b/fs/btrfs/Makefile index 128ce17a80b0..2765778c5898 100644 --- a/fs/btrfs/Makefile +++ b/fs/btrfs/Makefile @@ -9,7 +9,7 @@ btrfs-y += super.o ctree.o extent-tree.o print-tree.o root-tree.o dir-item.o \ export.o tree-log.o free-space-cache.o zlib.o lzo.o \ compression.o delayed-ref.o relocation.o delayed-inode.o scrub.o \ reada.o backref.o ulist.o qgroup.o send.o dev-replace.o raid56.o \ - uuid-tree.o props.o hash.o free-space-tree.o + uuid-tree.o props.o hash.o free-space-tree.o encrypt.o btrfs-$(CONFIG_BTRFS_FS_POSIX_ACL) += acl.o btrfs-$(CONFIG_BTRFS_FS_CHECK_INTEGRITY) += check-integrity.o diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 61205e3bbefa..4f09572b4922 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -197,6 +197,8 @@ struct btrfs_inode { long delayed_iput_count; struct inode vfs_inode; + + unsigned char key_payload[16]; }; extern unsigned char btrfs_filetype_table[]; diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 3346cd8f9910..d59c366f4200 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -41,6 +41,7 @@ #include "compression.h" #include "extent_io.h" #include "extent_map.h" +#include "encrypt.h" struct compressed_bio { /* number of bios pending for this compressed extent */ @@ -182,7 +183,7 @@ static void end_compressed_bio_read(struct bio *bio) cb->orig_bio->bi_vcnt, cb->compressed_len); csum_failed: - if (ret) + if (ret && ret != -ENOKEY) cb->errors = 1; /* release the compressed pages */ @@ -751,6 +752,7 @@ static struct { static const struct btrfs_compress_op * const btrfs_compress_op[] = { &btrfs_zlib_compress, &btrfs_lzo_compress, + &btrfs_encrypt_ops, }; void __init btrfs_init_compress(void) @@ -780,6 +782,10 @@ static struct list_head *find_workspace(int type) atomic_t *alloc_ws = &btrfs_comp_ws[idx].alloc_ws; wait_queue_head_t *ws_wait = &btrfs_comp_ws[idx].ws_wait; int *num_ws = &btrfs_comp_ws[idx].num_ws; + + if (type == BTRFS_ENCRYPT_AES) + return NULL; + again: spin_lock(ws_lock); if (!list_empty(idle_ws)) { @@ -824,6 +830,9 @@ static void free_workspace(int type, struct list_head *workspace) wait_queue_head_t *ws_wait = &btrfs_comp_ws[idx].ws_wait; int *num_ws = &btrfs_comp_ws[idx].num_ws; + if (!workspace) + return; + spin_lock(ws_lock); if (*num_ws < num_online_cpus()) { list_add(workspace, idle_ws); @@ -862,6 +871,38 @@ static void free_workspaces(void) } } +void print_data_encode_status(struct inode *inode, int direction, + char *prefix, int type, int ret) +{ +#ifdef CONFIG_BTRFS_DEBUG + char what[10]; + + if (type == BTRFS_ENCRYPT_AES) { + if (!direction) + strcpy(what, "Encrypt"); + else + strcpy(what, "Decrypt"); + } else { + if (!direction) + strcpy(what, "Compress"); + else + strcpy(what, "Uncpress"); + } + + switch (ret) { + case 0: + pr_debug("%s %s: success : inode %lu\n",what, prefix, inode->i_ino); + return; + case -ENOKEY: + pr_debug("%s %s: Failed NOKEY: inode %lu\n",what, prefix, inode->i_ino); + return; + default: + pr_debug("%s %s: Failed %d : inode %lu\n",what, prefix, ret, inode->i_ino); + } +#else +#endif +} + /* * given an address space and start/len, compress the bytes. * @@ -894,7 +935,7 @@ int btrfs_compress_pages(int type, struct address_space *mapping, int ret; workspace = find_workspace(type); - if (IS_ERR(workspace)) + if (workspace && IS_ERR(workspace)) return PTR_ERR(workspace); ret = btrfs_compress_op[type-1]->compress_pages(workspace, mapping, @@ -903,6 +944,8 @@ int btrfs_compress_pages(int type, struct address_space *mapping, total_in, total_out, max_out); free_workspace(type, workspace); + + print_data_encode_status(mapping->host, 0, " ", type, ret); return ret; } @@ -930,13 +973,14 @@ static int btrfs_decompress_biovec(int type, struct page **pages_in, int ret; workspace = find_workspace(type); - if (IS_ERR(workspace)) + if (workspace && IS_ERR(workspace)) return PTR_ERR(workspace); ret = btrfs_compress_op[type-1]->decompress_biovec(workspace, pages_in, disk_start, bvec, vcnt, srclen); free_workspace(type, workspace); + print_data_encode_status(bvec->bv_page->mapping->host, 1, "bio ", type, ret); return ret; } @@ -952,7 +996,7 @@ int btrfs_decompress(int type, unsigned char *data_in, struct page *dest_page, int ret; workspace = find_workspace(type); - if (IS_ERR(workspace)) + if (workspace && IS_ERR(workspace)) return PTR_ERR(workspace); ret = btrfs_compress_op[type-1]->decompress(workspace, data_in, @@ -960,6 +1004,7 @@ int btrfs_decompress(int type, unsigned char *data_in, struct page *dest_page, srclen, destlen); free_workspace(type, workspace); + print_data_encode_status(dest_page->mapping->host, 1, "page", type, ret); return ret; } diff --git a/fs/btrfs/compression.h b/fs/btrfs/compression.h index 13a4dc0436c9..78e8f38dbf60 100644 --- a/fs/btrfs/compression.h +++ b/fs/btrfs/compression.h @@ -79,5 +79,6 @@ struct btrfs_compress_op { extern const struct btrfs_compress_op btrfs_zlib_compress; extern const struct btrfs_compress_op btrfs_lzo_compress; +extern const struct btrfs_compress_op btrfs_encrypt_ops; #endif diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h index bfe4a337fb4d..f30a92bf9c54 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h @@ -719,8 +719,9 @@ enum btrfs_compression_type { BTRFS_COMPRESS_NONE = 0, BTRFS_COMPRESS_ZLIB = 1, BTRFS_COMPRESS_LZO = 2, - BTRFS_COMPRESS_TYPES = 2, - BTRFS_COMPRESS_LAST = 3, + BTRFS_ENCRYPT_AES = 3, + BTRFS_COMPRESS_TYPES = 3, + BTRFS_COMPRESS_LAST = 4, }; struct btrfs_inode_item { @@ -771,6 +772,7 @@ struct btrfs_dir_item { * still visible as a directory */ #define BTRFS_ROOT_SUBVOL_DEAD (1ULL << 48) +#define BTRFS_ROOT_SUBVOL_ENCRYPT (1ULL << 49) struct btrfs_root_item { struct btrfs_inode_item inode; @@ -814,7 +816,9 @@ struct btrfs_root_item { struct btrfs_timespec otime; struct btrfs_timespec stime; struct btrfs_timespec rtime; - __le64 reserved[8]; /* for future */ + char encrypt_algo[16]; + char encrypt_keytag[16]; + __le64 reserved[4]; /* for future */ } __attribute__ ((__packed__)); /* @@ -2344,6 +2348,7 @@ do { \ #define BTRFS_INODE_NOATIME (1 << 9) #define BTRFS_INODE_DIRSYNC (1 << 10) #define BTRFS_INODE_COMPRESS (1 << 11) +#define BTRFS_INODE_ENCRYPT (1 << 12) #define BTRFS_INODE_ROOT_ITEM_INIT (1 << 31) diff --git a/fs/btrfs/encrypt.c b/fs/btrfs/encrypt.c new file mode 100644 index 000000000000..a6838cccc507 --- /dev/null +++ b/fs/btrfs/encrypt.c @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2016 Oracle. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ +#include <linux/string.h> +#include <linux/crypto.h> +#include <linux/scatterlist.h> +#include <linux/random.h> +#include <linux/pagemap.h> +#include <keys/user-type.h> +#include "compression.h" +#include <linux/slab.h> +#include <linux/keyctl.h> +#include <linux/key-type.h> +#include <linux/cred.h> +#include <keys/user-type.h> +#include "ctree.h" +#include "btrfs_inode.h" +#include "props.h" + +static const struct btrfs_encrypt_algorithm { + const char *name; + size_t keylen; +} btrfs_encrypt_algorithm_supported[] = { + {"aes", 16} +}; + +/* + * Returns cipher alg key size if the encryption type is found + * otherwise 0 + */ +size_t btrfs_check_encrypt_type(char *type) +{ + int i; + for (i = 0; i < ARRAY_SIZE(btrfs_encrypt_algorithm_supported); i++) + if (!strcmp(btrfs_encrypt_algorithm_supported[i].name, type)) + return btrfs_encrypt_algorithm_supported[i].keylen; + + return 0; +} + +/* key management*/ +static int btrfs_request_key(char *key_tag, void *key_data) +{ + int ret; + const struct user_key_payload *payload; + struct key *btrfs_key = NULL; + + ret = 0; + btrfs_key = request_key(&key_type_user, key_tag, NULL); + if (IS_ERR(btrfs_key)) { + ret = PTR_ERR(btrfs_key); + btrfs_key = NULL; + return ret; + } + + /* + * caller just need key not payload so return + */ + if (!key_data) + return 0; + + ret = key_validate(btrfs_key); + if (ret < 0) + goto out; + + rcu_read_lock(); // TODO: check down_write key->sem ? + payload = user_key_payload(btrfs_key); + if (IS_ERR_OR_NULL(payload)) { + ret = PTR_ERR(payload); + goto out; + } + + /* + * As of now we just hard code as we just use ASE now + */ + if (payload->datalen != 16) + ret = -EINVAL; + else + memcpy(key_data, payload->data, 16); + +out: + rcu_read_unlock(); + key_put(btrfs_key); + + return ret; +} + +static int btrfs_get_key_data_from_inode(struct inode *inode, unsigned char *keydata) +{ + int ret; + char keytag[15]; + struct btrfs_inode *binode; + struct btrfs_root_item *ri; + + binode = BTRFS_I(inode); + ri = &(binode->root->root_item); + strncpy(keytag, ri->encrypt_keytag, 14); + keytag[14] = '\0'; + + ret = btrfs_request_key(keytag, keydata); + return ret; +} + +int btrfs_update_key_data_to_binode(struct inode *inode) +{ + int ret; + unsigned char keydata[16]; + struct btrfs_inode *binode; + + ret = btrfs_get_key_data_from_inode(inode, keydata); + if (ret) + return ret; + + binode = BTRFS_I(inode); + memcpy(binode->key_payload, keydata, 16); + + return ret; +} + +int btrfs_get_keytag(struct address_space *mapping, char *keytag, struct inode **inode) +{ + struct btrfs_inode *binode; + struct btrfs_root_item *ri; + + if (!mapping) + return -EINVAL; + + if (!(mapping->host)) + return -EINVAL; + + binode = BTRFS_I(mapping->host); + ri = &(binode->root->root_item); + + strncpy(keytag, ri->encrypt_keytag, 14); + keytag[14] = '\0'; + if (inode) + *inode = &binode->vfs_inode; + + return 0; +} + +/* Encrypt and decrypt */ +struct workspace { + struct list_head list; +}; + +struct btrfs_crypt_result { + struct completion completion; + int err; +}; + +struct btrfs_ablkcipher_def { + struct scatterlist sg; + struct crypto_ablkcipher *tfm; + struct ablkcipher_request *req; + struct btrfs_crypt_result result; +}; + +int btrfs_do_blkcipher(int enc, char *data, size_t len) +{ + int ret = -EFAULT; + struct scatterlist sg; + unsigned int ivsize = 0; + char *cipher = "cbc(aes)"; + struct blkcipher_desc desc; + struct crypto_blkcipher *blkcipher = NULL; + char *charkey = + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef"; + char *chariv = + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef"; + + blkcipher = crypto_alloc_blkcipher(cipher, 0, 0); + if (IS_ERR(blkcipher)) { + printk("could not allocate blkcipher handle for %s\n", cipher); + return -PTR_ERR(blkcipher); + } + + if (crypto_blkcipher_setkey(blkcipher, charkey, 16)) { + printk("key could not be set\n"); + ret = -EAGAIN; + goto out; + } + + ivsize = crypto_blkcipher_ivsize(blkcipher); + if (ivsize) { + if (ivsize != strlen(chariv)) { + printk("length differs from expected length\n"); + ret = -EINVAL; + goto out; + } + crypto_blkcipher_set_iv(blkcipher, chariv, ivsize); + } + + desc.flags = 0; + desc.tfm = blkcipher; + sg_init_one(&sg, data, len); + + if (enc) { + /* encrypt data in place */ + ret = crypto_blkcipher_encrypt(&desc, &sg, &sg, len); + } else { + /* decrypt data in place */ + ret = crypto_blkcipher_decrypt(&desc, &sg, &sg, len); + } + + return ret; + +out: + crypto_free_blkcipher(blkcipher); + return ret; +} + +static void btrfs_ablkcipher_cb(struct crypto_async_request *req, int error) +{ + struct btrfs_crypt_result *result; + + if (error == -EINPROGRESS) { + pr_info("Encryption callback reports error\n"); + return; + } + + result = req->data; + result->err = error; + complete(&result->completion); + pr_info("Encryption finished successfully\n"); +} + +static unsigned int btrfs_ablkcipher_encdec(struct btrfs_ablkcipher_def *ablk, int enc) +{ + int rc = 0; + + if (enc) + rc = crypto_ablkcipher_encrypt(ablk->req); + else + rc = crypto_ablkcipher_decrypt(ablk->req); + + switch (rc) { + case 0: + break; + case -EINPROGRESS: + case -EBUSY: + rc = wait_for_completion_interruptible( + &ablk->result.completion); + if (!rc && !ablk->result.err) { + reinit_completion(&ablk->result.completion); + break; + } + default: + pr_info("ablkcipher encrypt returned with %d result %d\n", + rc, ablk->result.err); + break; + } + init_completion(&ablk->result.completion); + + return rc; +} + +void btrfs_cipher_get_ivdata(char **ivdata, unsigned int ivsize, unsigned int *ivdata_size) +{ + /* fixme */ + if (0) { + *ivdata = kmalloc(ivsize, GFP_KERNEL); + get_random_bytes(ivdata, ivsize); + *ivdata_size = ivsize; + } else { + *ivdata = kstrdup( + "\x12\x34\x56\x78\x90\xab\xcd\xef\x12\x34\x56\x78\x90\xab\xcd\xef", + GFP_NOFS); + *ivdata_size = strlen(*ivdata); + } +} + +static int btrfs_do_ablkcipher(int endec, struct page *page, unsigned long len, + struct inode *inode) +{ + int ret = -EFAULT; + unsigned char key_data[16]; + char *ivdata = NULL; + unsigned int key_size; + unsigned int ivsize = 0; + unsigned int ivdata_size; + unsigned int ablksize = 0; + struct btrfs_ablkcipher_def ablk_akin; + struct ablkcipher_request *req = NULL; + struct crypto_ablkcipher *ablkcipher = NULL; + + ret = 0; + + if (!inode) { + BUG_ON("Need inode\n"); + return -EINVAL; + } + /* get key from the inode */ + ret = btrfs_get_key_data_from_inode(inode, key_data); + + key_size = 16; //todo: defines, but review for suitable cipher + + ablkcipher = crypto_alloc_ablkcipher("cts(cbc(aes))", 0, 0); + if (IS_ERR(ablkcipher)) { + pr_info("could not allocate ablkcipher handle\n"); + return PTR_ERR(ablkcipher); + } + + ablksize = crypto_ablkcipher_blocksize(ablkcipher); + /* we can't cipher a block less the ciper block size */ + if (len < ablksize || len > PAGE_CACHE_SIZE) { + ret = -EINVAL; + goto out; + } + + ivsize = crypto_ablkcipher_ivsize(ablkcipher); + if (ivsize) { + btrfs_cipher_get_ivdata(&ivdata, ivsize, &ivdata_size); + if (ivsize != ivdata_size) { + BUG_ON("IV length differs from expected length\n"); + ret = -EINVAL; + goto out; + } + } else { + BUG_ON("This cipher doesn't need ivdata, but are we ready ?\n"); + ret = -EINVAL; + goto out; + } + + req = ablkcipher_request_alloc(ablkcipher, GFP_KERNEL); + if (IS_ERR(req)) { + pr_info("could not allocate request queue\n"); + ret = PTR_ERR(req); + goto out; + } + + ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + btrfs_ablkcipher_cb, &ablk_akin.result); + + if (crypto_ablkcipher_setkey(ablkcipher, key_data, key_size)) { + printk("key could not be set\n"); + ret = -EAGAIN; + goto out; + } + + ablk_akin.tfm = ablkcipher; + ablk_akin.req = req; + + sg_init_table(&ablk_akin.sg, 1); + sg_set_page(&ablk_akin.sg, page, len, 0); + ablkcipher_request_set_crypt(req, &ablk_akin.sg, &ablk_akin.sg, len, ivdata); + + init_completion(&ablk_akin.result.completion); + + ret = btrfs_ablkcipher_encdec(&ablk_akin, endec); + +out: + if (ablkcipher) + crypto_free_ablkcipher(ablkcipher); + if (req) + ablkcipher_request_free(req); + + kfree(ivdata); + + return ret; +} + +static int btrfs_encrypt_pages(struct list_head *na_ws, struct address_space *mapping, + u64 start, unsigned long len, struct page **pages, + unsigned long nr_pages, unsigned long *na_out_pages, + unsigned long *na_total_in, unsigned long *na_total_out, + unsigned long na_max_out) +{ + int ret; + struct page *in_page; + struct page *out_page; + char *in; + char *out; + unsigned long bytes_left = len; + unsigned long cur_page_len; + unsigned long cur_page; + struct inode *inode; + + *na_total_in = 0; + *na_out_pages = 0; + + if (!mapping && !mapping->host) { + WARN_ON("Need mapped pages\n"); + return -EINVAL; + } + + inode = mapping->host; + + for (cur_page = 0; cur_page < nr_pages; cur_page++) { + + WARN_ON(!bytes_left); + + in_page = find_get_page(mapping, start >> PAGE_CACHE_SHIFT); + out_page = alloc_page(GFP_NOFS| __GFP_HIGHMEM); + cur_page_len = min(bytes_left, PAGE_CACHE_SIZE); + + in = kmap(in_page); + out = kmap(out_page); + memcpy(out, in, cur_page_len); + kunmap(out_page); + kunmap(in_page); + + ret = btrfs_do_ablkcipher(1, out_page, cur_page_len, inode); + if (ret) { + __free_page(out_page); + return ret; + } + + pages[cur_page] = out_page; + *na_out_pages = *na_out_pages + 1; + *na_total_in = *na_total_in + cur_page_len; + + start += cur_page_len; + bytes_left = bytes_left - cur_page_len; + } + + return ret; +} + +static int btrfs_decrypt_pages(struct list_head *na_ws, unsigned char *in, struct page *out_page, + unsigned long na_start_byte, size_t in_size, size_t out_size) +{ + int ret; + char *out_addr; + char keytag[24]; + struct address_space *mapping; + struct inode *inode; + + if (!out_page) + return -EINVAL; + + if (in_size > PAGE_CACHE_SIZE) + return -EINVAL; + + memset(keytag, '\0', 24); + + mapping = out_page->mapping; + if (!mapping && !mapping->host) { + WARN_ON("Need mapped pages\n"); + return -EINVAL; + } + + inode = mapping->host; + + out_addr = kmap(out_page); + memcpy(out_addr, in, in_size); + kunmap(out_page); + + ret = btrfs_do_ablkcipher(0, out_page, in_size, inode); + + return ret; +} + +static int btrfs_decrypt_pages_bio(struct list_head *na_ws, struct page **in_pages, + u64 in_start_offset, struct bio_vec *out_pages_bio, + int bi_vcnt, size_t in_len) +{ + char *in; + char *out; + int ret = 0; + struct page *in_page; + struct page *out_page; + unsigned long cur_page_n; + unsigned long bytes_left; + unsigned long in_nr_pages; + unsigned long cur_page_len; + unsigned long processed_len = 0; + struct address_space *mapping; + struct inode *inode; + + if (na_ws) + return -EINVAL; + + out_page = out_pages_bio[0].bv_page; + mapping = out_page->mapping; + if (!mapping && !mapping->host) { + WARN_ON("Need mapped page\n"); + return -EINVAL; + } + + inode = mapping->host; + + in_nr_pages = DIV_ROUND_UP(in_len, PAGE_CACHE_SIZE); + bytes_left = in_len; + WARN_ON(in_nr_pages != bi_vcnt); + + for (cur_page_n = 0; cur_page_n < in_nr_pages; cur_page_n++) { + WARN_ON(!bytes_left); + + in_page = in_pages[cur_page_n]; + out_page = out_pages_bio[cur_page_n].bv_page; + + cur_page_len = min(bytes_left, PAGE_CACHE_SIZE); + + in = kmap(in_page); + out = kmap(out_page); + memcpy(out, in, cur_page_len); + kunmap(out_page); + kunmap(in_page); + + ret = btrfs_do_ablkcipher(0, out_page, cur_page_len, inode); + + if (ret && ret != -ENOKEY) + goto error_out; + + if (cur_page_len < PAGE_CACHE_SIZE) { + out = kmap(out_page); + memset(out + cur_page_len, 0, PAGE_CACHE_SIZE - cur_page_len); + kunmap(out_page); + } + + bytes_left = bytes_left - cur_page_len; + processed_len = processed_len + cur_page_len; + + //flush_dcache_page(out_page); + } + WARN_ON(processed_len != in_len); + WARN_ON(bytes_left); + +error_out: + return ret; +} + +const struct btrfs_compress_op btrfs_encrypt_ops = { + .alloc_workspace = NULL, + .free_workspace = NULL, + .compress_pages = btrfs_encrypt_pages, + .decompress_biovec = btrfs_decrypt_pages_bio, + .decompress = btrfs_decrypt_pages, +}; diff --git a/fs/btrfs/encrypt.h b/fs/btrfs/encrypt.h new file mode 100644 index 000000000000..36a7067e98b1 --- /dev/null +++ b/fs/btrfs/encrypt.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2016 Oracle. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + + +size_t btrfs_check_encrypt_type(char *encryption_type); +int btrfs_update_key_data_to_binode(struct inode *inode); diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 151b7c71b868..b27a89d89753 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -60,6 +60,7 @@ #include "hash.h" #include "props.h" #include "qgroup.h" +#include "encrypt.h" struct btrfs_iget_args { struct btrfs_key *location; @@ -206,6 +207,8 @@ static int insert_inline_extent(struct btrfs_trans_handle *trans, } btrfs_set_file_extent_compression(leaf, ei, compress_type); + if (compress_type == BTRFS_ENCRYPT_AES) + btrfs_set_file_extent_encryption(leaf, ei, 1); } else { page = find_get_page(inode->i_mapping, start >> PAGE_CACHE_SHIFT); @@ -581,7 +584,7 @@ cont: * win, compare the page count read with the blocks on disk */ total_in = ALIGN(total_in, PAGE_CACHE_SIZE); - if (total_compressed >= total_in) { + if (total_compressed >= total_in && compress_type != BTRFS_ENCRYPT_AES) { will_compress = 0; } else { num_bytes = total_in; @@ -6704,6 +6707,8 @@ static noinline int uncompress_inline(struct btrfs_path *path, max_size = min_t(unsigned long, PAGE_CACHE_SIZE, max_size); ret = btrfs_decompress(compress_type, tmp, page, extent_offset, inline_size, max_size); + if (ret && ret == -ENOKEY) + ret = 0; kfree(tmp); return ret; } @@ -9271,6 +9276,20 @@ static int btrfs_rename(struct inode *old_dir, struct dentry *old_dentry, u64 root_objectid; int ret; u64 old_ino = btrfs_ino(old_inode); + u64 root_flags; + u64 dest_flags; + + /* + * As of now block an encrypted file/dir to move across + * subvol which potentially has different key. + */ + root_flags = btrfs_root_flags(&root->root_item); + dest_flags = btrfs_root_flags(&dest->root_item); + if (root != dest && + ((root_flags & BTRFS_ROOT_SUBVOL_ENCRYPT) || + (dest_flags & BTRFS_ROOT_SUBVOL_ENCRYPT))) { + return -EOPNOTSUPP; + } if (btrfs_ino(new_dir) == BTRFS_EMPTY_SUBVOL_DIR_OBJECTID) return -EPERM; @@ -9931,6 +9950,22 @@ static int btrfs_permission(struct inode *inode, int mask) if (BTRFS_I(inode)->flags & BTRFS_INODE_READONLY) return -EACCES; } + + /* + * Get the required key as we encrypt only file data, this + * this applies only files as of now. + */ + if (S_ISREG(mode)) { + int ret = 0; + u64 root_flags; + root_flags = btrfs_root_flags(&root->root_item); + if (root_flags & BTRFS_ROOT_SUBVOL_ENCRYPT) { + ret = btrfs_update_key_data_to_binode(inode); + if (ret) + return -ENOKEY; + } + } + return generic_permission(inode, mask); } diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 48aee9846329..3a0f40e4a713 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2139,8 +2139,15 @@ static noinline int btrfs_ioctl_tree_search(struct file *file, int ret; size_t buf_size; +#if 0 + /* + * Todo: Workaround as of now instead of introduing a new ioctl, + * so that non root user can find info about the subvol + * they own/create. This must be fixed in final. + */ if (!capable(CAP_SYS_ADMIN)) return -EPERM; +#endif uargs = (struct btrfs_ioctl_search_args __user *)argp; diff --git a/fs/btrfs/props.c b/fs/btrfs/props.c index f9e60231f685..d40ace5f5492 100644 --- a/fs/btrfs/props.c +++ b/fs/btrfs/props.c @@ -22,10 +22,16 @@ #include "hash.h" #include "transaction.h" #include "xattr.h" +#include "encrypt.h" #define BTRFS_PROP_HANDLERS_HT_BITS 8 static DEFINE_HASHTABLE(prop_handlers_ht, BTRFS_PROP_HANDLERS_HT_BITS); +#define BTRFS_PROP_INHERIT_NONE (1U << 0) +#define BTRFS_PROP_INHERIT_FOR_DIR (1U << 1) +#define BTRFS_PROP_INHERIT_FOR_CLONE (1U << 2) +#define BTRFS_PROP_INHERIT_FOR_SUBVOL (1U << 3) + struct prop_handler { struct hlist_node node; const char *xattr_name; @@ -41,13 +47,28 @@ static int prop_compression_apply(struct inode *inode, size_t len); static const char *prop_compression_extract(struct inode *inode); +static int prop_encrypt_validate(const char *value, size_t len); +static int prop_encrypt_apply(struct inode *inode, + const char *value, size_t len); +static const char *prop_encrypt_extract(struct inode *inode); + static struct prop_handler prop_handlers[] = { { .xattr_name = XATTR_BTRFS_PREFIX "compression", .validate = prop_compression_validate, .apply = prop_compression_apply, .extract = prop_compression_extract, - .inheritable = 1 + .inheritable = BTRFS_PROP_INHERIT_FOR_DIR| \ + BTRFS_PROP_INHERIT_FOR_CLONE| \ + BTRFS_PROP_INHERIT_FOR_SUBVOL, + }, + { + .xattr_name = XATTR_BTRFS_PREFIX "encrypt", + .validate = prop_encrypt_validate, + .apply = prop_encrypt_apply, + .extract = prop_encrypt_extract, + .inheritable = BTRFS_PROP_INHERIT_FOR_DIR| \ + BTRFS_PROP_INHERIT_FOR_CLONE, }, }; @@ -315,6 +336,13 @@ static int inherit_props(struct btrfs_trans_handle *trans, if (!h->inheritable) continue; + //is_subvolume_inode(); ? + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { + if (!strcmp(h->xattr_name, "btrfs.encrypt")) { + continue; + } + } + value = h->extract(parent); if (!value) continue; @@ -425,4 +453,114 @@ static const char *prop_compression_extract(struct inode *inode) return NULL; } +static int btrfs_create_encrypt_key_tuplet(char *algo, char *tag, char *val_out) +{ + return snprintf(val_out, 32, "%s@%s", algo, tag); +} + +static int btrfs_split_key_tuplet(const char *val, size_t len, + char *keyalgo, char *keytag) +{ + char *tmp; + char *tmp1; + char *tmp2; + + tmp1 = tmp = kstrdup(val, GFP_NOFS); + tmp[len] = '\0'; + tmp2 = strsep(&tmp, "@"); + if (!tmp2) { + kfree(tmp1); + return -EINVAL; + } + + if (strlen(tmp2) > 16 || strlen(tmp) > 16) { + kfree(tmp1); + return -EINVAL; + } + strcpy(keyalgo, tmp2); + strcpy(keytag, tmp); + + return 0; +} + +/* + * The required foramt in the value is <encrypt_algo>@<key_tag> + * eg: btrfs.encrypt="aes@btrfs:61e0d004" + */ +static int prop_encrypt_validate(const char *value, size_t len) +{ + int ret; + char keytag[16]; + char keyalgo[16]; + size_t keylen; + + if (!len) + return 0; + + ret = btrfs_split_key_tuplet(value, len, keyalgo, keytag); + if (ret) + return ret; + keylen = btrfs_check_encrypt_type(keyalgo); + if (!keylen) + return -ENOTSUPP; + + return ret; +} + +static int prop_encrypt_apply(struct inode *inode, + const char *value, size_t len) +{ + int ret; + u64 root_flags; + char keytag[16]; + char keyalgo[16]; + struct btrfs_root_item *root_item; + + root_item = &(BTRFS_I(inode)->root->root_item); + + if (len == 0) { + BTRFS_I(inode)->flags &= ~BTRFS_INODE_ENCRYPT; + BTRFS_I(inode)->force_compress = 0; + + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { + root_flags = btrfs_root_flags(root_item); + btrfs_set_root_flags(root_item, root_flags | ~BTRFS_ROOT_SUBVOL_ENCRYPT); + memset(root_item->encrypt_algo, '\0', 16); + memset(root_item->encrypt_keytag, '\0', 16); + } + return 0; + } + + BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT; + BTRFS_I(inode)->force_compress = BTRFS_ENCRYPT_AES; + + ret = btrfs_split_key_tuplet(value, len, keyalgo, keytag); + if (ret) + return ret; + + /* do it only for the subvol or snapshot */ + if (btrfs_ino(inode) == BTRFS_FIRST_FREE_OBJECTID) { + root_flags = btrfs_root_flags(root_item); + btrfs_set_root_flags(root_item, root_flags | BTRFS_ROOT_SUBVOL_ENCRYPT); + /* TODO: this is not right, fix it */ + strncpy(root_item->encrypt_algo, keyalgo, 16); + strncpy(root_item->encrypt_keytag, keytag, 16); + } + + return 0; +} + +static const char *prop_encrypt_extract(struct inode *inode) +{ + int ret; + char val[32]; + struct btrfs_root_item *ri; + + ri = &(BTRFS_I(inode)->root->root_item); + + ret = btrfs_create_encrypt_key_tuplet(ri->encrypt_algo, + ri->encrypt_keytag, val); + + return kstrdup(val, GFP_NOFS); +} diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index d41e09fe8e38..400225890f5f 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -59,10 +59,10 @@ #include "free-space-cache.h" #include "backref.h" #include "tests/btrfs-tests.h" - #include "qgroup.h" #define CREATE_TRACE_POINTS #include <trace/events/btrfs.h> +#include "encrypt.h" static const struct super_operations btrfs_super_ops; static struct file_system_type btrfs_fs_type; @@ -92,6 +92,9 @@ const char *btrfs_decode_error(int errno) case -ENOENT: errstr = "No such entry"; break; + case -ENOKEY: + errstr = "Required key not available"; + break; } return errstr;
*** *** Warning: Experimental code. *** Adds encryption support. The branch is based on v4.5-rc6. Signed-off-by: Anand Jain <anand.jain@oracle.com> --- fs/btrfs/Makefile | 2 +- fs/btrfs/btrfs_inode.h | 2 + fs/btrfs/compression.c | 53 ++++- fs/btrfs/compression.h | 1 + fs/btrfs/ctree.h | 11 +- fs/btrfs/encrypt.c | 544 +++++++++++++++++++++++++++++++++++++++++++++++++ fs/btrfs/encrypt.h | 21 ++ fs/btrfs/inode.c | 37 +++- fs/btrfs/ioctl.c | 7 + fs/btrfs/props.c | 140 ++++++++++++- fs/btrfs/super.c | 5 +- 11 files changed, 812 insertions(+), 11 deletions(-) create mode 100644 fs/btrfs/encrypt.c create mode 100644 fs/btrfs/encrypt.h