diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c index 6592279d839aff6d267cd225e5e51f0e6bbe0ea2..2a2b14d1c3d6a99d0a5b884a919b2795b869ea8e 100644 --- a/crypto/asymmetric_keys/pkcs7_parser.c +++ b/crypto/asymmetric_keys/pkcs7_parser.c @@ -526,6 +526,17 @@ int pkcs7_sig_note_authenticated_attr(void *context, size_t hdrlen, } return 0; +#ifdef CONFIG_SECURITY_CODE_SIGN + case OID_ownerid: + if (__test_and_set_bit(sinfo_has_owner_identifier, &sinfo->aa_set)) + goto repeated; + if (tag != ASN1_UTF8STR) + return -EBADMSG; + sinfo->ownerid = value; + sinfo->ownerid_len = vlen; + return 0; +#endif /* CONFIG_SECURITY_CODE_SIGN */ + /* Microsoft SpOpusInfo seems to be contain cont[0] 16-bit BE * char URLs and cont[1] 8-bit char URLs. * diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h index e17f7ce4fb434832a3867ce2254de6ccc7f06ae7..19e7d989ee7e2c8df24c64345142754e7b247565 100644 --- a/crypto/asymmetric_keys/pkcs7_parser.h +++ b/crypto/asymmetric_keys/pkcs7_parser.h @@ -35,8 +35,14 @@ struct pkcs7_signed_info { #define sinfo_has_smime_caps 3 #define sinfo_has_ms_opus_info 4 #define sinfo_has_ms_statement_type 5 +#define sinfo_has_owner_identifier 6 time64_t signing_time; +#ifdef CONFIG_SECURITY_CODE_SIGN + const char *ownerid; + unsigned ownerid_len; +#endif /* CONFIG_SECURITY_CODE_SIGN */ + /* Message signature. * * This contains the generated digest of _either_ the Content Data or diff --git a/fs/Makefile b/fs/Makefile index 84c5e4cdfee5a694da7fc96b6a08109d653e4cb5..c57f8ccf598d1a2e5230b2f6936b2cf123e8fccc 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_IO_URING) += io_uring.o obj-$(CONFIG_IO_WQ) += io-wq.o obj-$(CONFIG_FS_DAX) += dax.o obj-$(CONFIG_FS_ENCRYPTION) += crypto/ +obj-$(CONFIG_SECURITY_CODE_SIGN) += code_sign/ obj-$(CONFIG_FS_VERITY) += verity/ obj-$(CONFIG_FILE_LOCKING) += locks.o obj-$(CONFIG_BINFMT_AOUT) += binfmt_aout.o diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 4cf522120cb1adf9ad2bb0b950d5f6b0c0ee6dd6..229455f681b77c22492e5ba51970acfb8b73758f 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -976,6 +976,12 @@ out: up_write(&io->io_rwsem); } +static inline bool f2fs_need_verity_code_sign(const struct inode *inode, pgoff_t idx) +{ + return fsverity_active(inode) && (idx < + DIV_ROUND_UP(fsverity_get_verified_data_size(inode), PAGE_SIZE)); +} + static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr, unsigned nr_pages, unsigned op_flag, pgoff_t first_idx, bool for_write) @@ -999,7 +1005,7 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr, if (fscrypt_inode_uses_fs_layer_crypto(inode)) post_read_steps |= STEP_DECRYPT; - if (f2fs_need_verity(inode, first_idx)) + if (f2fs_need_verity_code_sign(inode, first_idx)) post_read_steps |= STEP_VERITY; /* @@ -2088,7 +2094,7 @@ got_it: } else { zero_out: zero_user_segment(page, 0, PAGE_SIZE); - if (f2fs_need_verity(inode, page->index) && + if (f2fs_need_verity_code_sign(inode, page->index) && !fsverity_verify_page(page)) { ret = -EIO; goto out; diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 758048a885d2418c8fad4ce540dfba17f7c46d91..022b371e344491890a7cbfaf5b1054b06b35a3e4 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3253,6 +3253,21 @@ static int f2fs_ioc_resize_fs(struct file *filp, unsigned long arg) return f2fs_resize_fs(sbi, block_count); } +static inline int f2fs_has_feature_verity(struct file *filp) +{ + struct inode *inode = file_inode(filp); + + f2fs_update_time(F2FS_I_SB(inode), REQ_TIME); + + if (!f2fs_sb_has_verity(F2FS_I_SB(inode))) { + f2fs_warn(F2FS_I_SB(inode), + "Can't enable fs-verity on inode %lu: the verity feature is not enabled on this filesystem.\n", + inode->i_ino); + return -EOPNOTSUPP; + } + return 0; +} + static int f2fs_ioc_enable_verity(struct file *filp, unsigned long arg) { struct inode *inode = file_inode(filp); @@ -3269,6 +3284,16 @@ static int f2fs_ioc_enable_verity(struct file *filp, unsigned long arg) return fsverity_ioctl_enable(filp, (const void __user *)arg); } +static int f2fs_ioc_enable_code_sign(struct file *filp, unsigned long arg) +{ + int err = f2fs_has_feature_verity(filp); + + if (err) + return err; + + return fsverity_ioctl_enable_code_sign(filp, (const void __user *)arg); +} + static int f2fs_ioc_measure_verity(struct file *filp, unsigned long arg) { if (!f2fs_sb_has_verity(F2FS_I_SB(file_inode(filp)))) @@ -4190,6 +4215,8 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return f2fs_ioc_enable_verity(filp, arg); case FS_IOC_MEASURE_VERITY: return f2fs_ioc_measure_verity(filp, arg); + case FS_IOC_ENABLE_CODE_SIGN: + return f2fs_ioc_enable_code_sign(filp, arg); case FS_IOC_READ_VERITY_METADATA: return f2fs_ioc_read_verity_metadata(filp, arg); case FS_IOC_GETFSLABEL: @@ -4479,6 +4506,7 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case F2FS_IOC_RESIZE_FS: case FS_IOC_ENABLE_VERITY: case FS_IOC_MEASURE_VERITY: + case FS_IOC_ENABLE_CODE_SIGN: case FS_IOC_READ_VERITY_METADATA: case FS_IOC_GETFSLABEL: case FS_IOC_SETFSLABEL: diff --git a/fs/verity/enable.c b/fs/verity/enable.c index 60a4372aa4d75f5157865bbf525bfb1a2cd4bb7e..57f6a4ab822032f3da5cbbd179edd4264075152d 100644 --- a/fs/verity/enable.c +++ b/fs/verity/enable.c @@ -14,6 +14,30 @@ #include #include +#ifdef CONFIG_SECURITY_CODE_SIGN + +static int code_sign_init_descriptor(struct inode *inode, + const struct fsverity_enable_arg *_arg, struct fsverity_descriptor *_desc); + +static int code_sign_copy_merkle_tree(struct file *filp, const void *_desc, + const struct merkle_tree_params *params); + +#else /* !CONFIG_SECURITY_CODE_SIGN */ + +static inline int code_sign_init_descriptor(struct inode *inode, + const struct fsverity_enable_arg *_arg, struct fsverity_descriptor *_desc) +{ + return 0; +} + +static int code_sign_copy_merkle_tree(struct file *filp, + const void *_desc, + const struct merkle_tree_params *params) +{ + return 0; +} +#endif /* !CONFIG_SECURITY_CODE_SIGN */ + /* * Read a file data page for Merkle tree construction. Do aggressive readahead, * since we're sequentially reading the entire file. @@ -150,16 +174,16 @@ static int build_merkle_tree_level(struct file *filp, unsigned int level, */ static int build_merkle_tree(struct file *filp, const struct merkle_tree_params *params, - u8 *root_hash) + u8 *root_hash, + size_t data_size) { - struct inode *inode = file_inode(filp); u8 *pending_hashes; struct ahash_request *req; u64 blocks; unsigned int level; int err = -ENOMEM; - if (inode->i_size == 0) { + if (data_size == 0) { /* Empty file is a special case; root hash is all 0's */ memset(root_hash, 0, params->digest_size); return 0; @@ -177,7 +201,7 @@ static int build_merkle_tree(struct file *filp, * (level 0) and ascending to the root node (level 'num_levels - 1'). * Then at the end (level 'num_levels'), calculate the root hash. */ - blocks = ((u64)inode->i_size + params->block_size - 1) >> + blocks = ((u64)data_size + params->block_size - 1) >> params->log_blocksize; for (level = 0; level <= params->num_levels; level++) { err = build_merkle_tree_level(filp, level, blocks, params, @@ -199,11 +223,8 @@ static int enable_verity(struct file *filp, const struct fsverity_enable_arg *arg) { struct inode *inode = file_inode(filp); - const struct fsverity_operations *vops = inode->i_sb->s_vop; - struct merkle_tree_params params = { }; struct fsverity_descriptor *desc; size_t desc_size = sizeof(*desc) + arg->sig_size; - struct fsverity_info *vi; int err; /* Start initializing the fsverity_descriptor */ @@ -234,11 +255,34 @@ static int enable_verity(struct file *filp, desc->data_size = cpu_to_le64(inode->i_size); + err = code_sign_init_descriptor(inode, arg, desc); + if (err) { + fsverity_err(inode, "Init code sign descriptor err: %u", err); + goto out; + } + + err = fsverity_enable_with_descriptor(filp, (void *)desc, desc_size); +out: + kfree(desc); + return err; +} + +int fsverity_enable_with_descriptor(struct file *filp, + void *_desc, size_t desc_size) +{ + struct inode *inode = file_inode(filp); + const struct fsverity_operations *vops = inode->i_sb->s_vop; + struct merkle_tree_params params = { }; + struct fsverity_descriptor *desc = (struct fsverity_descriptor *)_desc; + struct fsverity_info *vi; + int err; + /* Prepare the Merkle tree parameters */ err = fsverity_init_merkle_tree_params(¶ms, inode, - arg->hash_algorithm, + desc->hash_algorithm, desc->log_blocksize, - desc->salt, desc->salt_size); + desc->salt, desc->salt_size, + desc->data_size); if (err) goto out; @@ -255,6 +299,13 @@ static int enable_verity(struct file *filp, if (err) goto out; + err = code_sign_copy_merkle_tree(filp, _desc, ¶ms); + if (err < 0) { + fsverity_err(inode, "Error %d copying Merkle tree", err); + goto rollback; + } else if (err == 1) /* already copy merkle tree */ + goto skip_build; + /* * Build the Merkle tree. Don't hold the inode lock during this, since * on huge files this may take a very long time and we don't want to @@ -266,11 +317,13 @@ static int enable_verity(struct file *filp, */ pr_debug("Building Merkle tree...\n"); BUILD_BUG_ON(sizeof(desc->root_hash) < FS_VERITY_MAX_DIGEST_SIZE); - err = build_merkle_tree(filp, ¶ms, desc->root_hash); + err = build_merkle_tree(filp, ¶ms, desc->root_hash, desc->data_size); if (err) { fsverity_err(inode, "Error %d building Merkle tree", err); goto rollback; } + +skip_build: pr_debug("Done building Merkle tree. Root hash is %s:%*phN\n", params.hash_alg->name, params.digest_size, desc->root_hash); @@ -287,9 +340,9 @@ static int enable_verity(struct file *filp, goto rollback; } - if (arg->sig_size) + if (desc->sig_size) pr_debug("Storing a %u-byte PKCS#7 signature alongside the file\n", - arg->sig_size); + desc->sig_size); /* * Tell the filesystem to finish enabling verity on the file. @@ -317,7 +370,6 @@ static int enable_verity(struct file *filp, } out: kfree(params.hashstate); - kfree(desc); return err; rollback: @@ -326,6 +378,7 @@ rollback: inode_unlock(inode); goto out; } +EXPORT_SYMBOL_GPL(fsverity_enable_with_descriptor); /** * fsverity_ioctl_enable() - enable verity on a file @@ -416,3 +469,204 @@ out_drop_write: return err; } EXPORT_SYMBOL_GPL(fsverity_ioctl_enable); + +#ifdef CONFIG_SECURITY_CODE_SIGN +static int code_sign_copy_merkle_tree(struct file *filp, + const void *_desc, + const struct merkle_tree_params *params) +{ + struct inode *inode = file_inode(filp); + const struct fsverity_operations *vops = inode->i_sb->s_vop; + u8 *tree_data; + u64 blocks, i; + int err = -ENOMEM; + struct file_ra_state ra = { 0 }; + struct page *src_page; + void *addr; + u64 tree_offset, tree_start_index; + + if (!is_inside_tree_compact(_desc)) + return 0; + + tree_offset = get_tree_offset_compact(_desc); + + if (inode->i_size < tree_offset + params->tree_size) { + fsverity_err(inode, "File is too small to contain Merkle tree."); + return -EFAULT; + } + + tree_data = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tree_data) + goto out; + + file_ra_state_init(&ra, filp->f_mapping); + + tree_start_index = tree_offset >> PAGE_SHIFT; + blocks = params->tree_size >> PAGE_SHIFT; + for (i = 0; i < blocks; i++) { + pr_debug("Copy Merkle tree page at %d\n", tree_start_index + i); + src_page = read_file_data_page(filp, tree_start_index + i, &ra, + blocks - i); + if (IS_ERR(src_page)) { + err = PTR_ERR(src_page); + fsverity_err(inode, + "Error %d reading Merkle tree page %llu", + err, tree_start_index + i); + goto out; + } + + addr = kmap_atomic(src_page); + memcpy(tree_data, addr, PAGE_SIZE); + kunmap_atomic(addr); + put_page(src_page); + err = vops->write_merkle_tree_block(inode, tree_data, i, + params->log_blocksize); + if (err) { + fsverity_err(inode, + "Error %d writing Merkle tree block %llu", + err, i); + goto out; + } + } + /* already copy merkle tree */ + err = 1; +out: + kfree(tree_data); + return err; +} + +static int code_sign_init_descriptor(struct inode *inode, + const struct fsverity_enable_arg *_arg, + struct fsverity_descriptor *_desc) +{ + struct code_sign_descriptor *desc = CAST_CODE_SIGN_DESC(_desc); + const struct code_sign_enable_arg *arg = (const struct code_sign_enable_arg *)_arg; + int algo_index; + + if (!arg->cs_version) + return 0; + + /* init extended fields */ + desc->flags = cpu_to_le32(arg->flags); + desc->data_size = cpu_to_le64(arg->data_size); + desc->tree_offset = cpu_to_le64(arg->tree_offset); + desc->cs_version = arg->cs_version; + + /* Get root hash if a Merkle tree carried in file */ + if (!IS_INSIDE_TREE(desc)) + return 0; + + /* Get size of root hash */ + algo_index = desc->hash_algorithm; + if (algo_index >= g_fsverity_hash_algs_num || + !fsverity_hash_algs[algo_index].name) { + fsverity_err(inode, "Unknown hash algorithm: %u", algo_index); + return -EINVAL; + } + + if (copy_from_user(desc->root_hash, u64_to_user_ptr(arg->root_hash_ptr), + fsverity_hash_algs[algo_index].digest_size)) { + return -EFAULT; + } + + return 0; +} + +/** + * fsverity_ioctl_enable_code_sign() - enable code signing on a file + * @filp: file to enable code signing on + * @uarg: user pointer to code_sign_enable_arg + * + * Enable fs-verity on a file with code signing features. + * + * Return: 0 on success, -errno on failure + */ +int fsverity_ioctl_enable_code_sign(struct file *filp, const void __user *uarg) +{ + struct inode *inode = file_inode(filp); + struct code_sign_enable_arg arg; + int err; + + if (copy_from_user(&arg, uarg, sizeof(arg))) + return -EFAULT; + + if (arg.version != 1) + return -EINVAL; + + if (arg.cs_version != 1) + return -EINVAL; + + if (arg.__reserved1 || + memchr_inv(arg.__reserved2, 0, sizeof(arg.__reserved2))) + return -EINVAL; + + if (arg.data_size > inode->i_size) + return -EINVAL; + + if (arg.tree_offset % PAGE_SIZE != 0) + return -EINVAL; + + if (arg.block_size != PAGE_SIZE) + return -EINVAL; + + if (arg.salt_size > sizeof_field(struct code_sign_descriptor, salt)) + return -EMSGSIZE; + + if (arg.sig_size > FS_VERITY_MAX_SIGNATURE_SIZE) + return -EMSGSIZE; + + /* + * Require a regular file with write access. But the actual fd must + * still be readonly so that we can lock out all writers. This is + * needed to guarantee that no writable fds exist to the file once it + * has verity enabled, and to stabilize the data being hashed. + */ + + err = file_permission(filp, MAY_WRITE); + if (err) + return err; + + if (IS_APPEND(inode)) + return -EPERM; + + if (S_ISDIR(inode->i_mode)) + return -EISDIR; + + if (!S_ISREG(inode->i_mode)) + return -EINVAL; + + err = mnt_want_write_file(filp); + if (err) /* -EROFS */ + return err; + + err = deny_write_access(filp); + if (err) /* -ETXTBSY */ + goto out_drop_write; + + err = enable_verity(filp, (struct fsverity_enable_arg *)&arg); + if (err) + goto out_allow_write_access; + + /* + * Some pages of the file may have been evicted from pagecache after + * being used in the Merkle tree construction, then read into pagecache + * again by another process reading from the file concurrently. Since + * these pages didn't undergo verification against the file digest which + * fs-verity now claims to be enforcing, we have to wipe the pagecache + * to ensure that all future reads are verified. + */ + filemap_write_and_wait(inode->i_mapping); + invalidate_inode_pages2(inode->i_mapping); + + /* + * allow_write_access() is needed to pair with deny_write_access(). + * Regardless, the filesystem won't allow writing to verity files. + */ +out_allow_write_access: + allow_write_access(filp); +out_drop_write: + mnt_drop_write_file(filp); + return err; +} +EXPORT_SYMBOL_GPL(fsverity_ioctl_enable_code_sign); +#endif /* CONFIG_SECURITY_CODE_SIGN */ diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h index a7920434bae50c3112ffb1c870549fe6aa0477d1..1c71a008d289599b695479694691e81c4b07ef78 100644 --- a/fs/verity/fsverity_private.h +++ b/fs/verity/fsverity_private.h @@ -12,11 +12,12 @@ #define DEBUG #endif -#define pr_fmt(fmt) "fs-verity: " fmt +//#define pr_fmt(fmt) "fs-verity: " fmt #include #include #include +#include struct ahash_request; @@ -75,8 +76,19 @@ struct fsverity_info { u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE]; u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE]; const struct inode *inode; +#ifdef CONFIG_SECURITY_CODE_SIGN + struct cs_info fcs_info; + u64 verified_data_size; + int cert_type; +#endif }; +struct fsverity_signed_digest { + char magic[8]; /* must be "FSVerity" */ + __le16 digest_algorithm; + __le16 digest_size; + __u8 digest[]; +}; /* Arbitrary limit to bound the kmalloc() size. Can be changed. */ #define FS_VERITY_MAX_DESCRIPTOR_SIZE 16384 @@ -87,6 +99,8 @@ struct fsverity_info { extern struct fsverity_hash_alg fsverity_hash_algs[]; +extern int g_fsverity_hash_algs_num; + struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode, unsigned int num); struct ahash_request *fsverity_alloc_hash_request(struct fsverity_hash_alg *alg, @@ -119,7 +133,8 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params, const struct inode *inode, unsigned int hash_algorithm, unsigned int log_blocksize, - const u8 *salt, size_t salt_size); + const u8 *salt, size_t salt_size, + u64 data_size); struct fsverity_info *fsverity_create_info(const struct inode *inode, struct fsverity_descriptor *desc, diff --git a/fs/verity/hash_algs.c b/fs/verity/hash_algs.c index 71d0fccb6d4c44ffc09bf7fef3ac416dfe3ff21a..87d88e44da33b4a137e160c383eae28ee30e0e05 100644 --- a/fs/verity/hash_algs.c +++ b/fs/verity/hash_algs.c @@ -24,6 +24,8 @@ struct fsverity_hash_alg fsverity_hash_algs[] = { }, }; +int g_fsverity_hash_algs_num = ARRAY_SIZE(fsverity_hash_algs); + static DEFINE_MUTEX(fsverity_hash_alg_init_mutex); /** diff --git a/fs/verity/open.c b/fs/verity/open.c index 92df87f5fa3881abec4f18d20686cc991558ced4..cf4fb36dd4b2bc747cbd0721222db7ba970cd616 100644 --- a/fs/verity/open.c +++ b/fs/verity/open.c @@ -29,7 +29,8 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params, const struct inode *inode, unsigned int hash_algorithm, unsigned int log_blocksize, - const u8 *salt, size_t salt_size) + const u8 *salt, size_t salt_size, + u64 data_size) { struct fsverity_hash_alg *hash_alg; int err; @@ -89,8 +90,8 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params, */ /* Compute number of levels and the number of blocks in each level */ - blocks = ((u64)inode->i_size + params->block_size - 1) >> log_blocksize; - pr_debug("Data is %lld bytes (%llu blocks)\n", inode->i_size, blocks); + blocks = ((u64)data_size + params->block_size - 1) >> params->log_blocksize; + pr_debug("Data is %lld bytes (%llu blocks)\n", data_size, blocks); while (blocks > 1) { if (params->num_levels >= FS_VERITY_MAX_LEVELS) { fsverity_err(inode, "Too many levels in Merkle tree"); @@ -132,11 +133,13 @@ static int compute_file_digest(struct fsverity_hash_alg *hash_alg, u8 *file_digest) { __le32 sig_size = desc->sig_size; - int err; + int err, cs_version; + cs_version = code_sign_before_measurement_hook(desc); desc->sig_size = 0; err = fsverity_hash_buffer(hash_alg, desc, sizeof(*desc), file_digest); desc->sig_size = sig_size; + code_sign_after_measurement_hook(desc, cs_version); return err; } @@ -153,15 +156,57 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode, struct fsverity_info *vi; int err; + if (desc_size < sizeof(*desc)) { + fsverity_err(inode, "Unrecognized descriptor size: %zu bytes", + desc_size); + return ERR_PTR(-EINVAL); + } + + err = code_sign_check_descriptor_hook(inode, (void *)desc); + if (err < 0) { + fsverity_err(inode, "Invalid code sign descriptor."); + return ERR_PTR(err); + } else if (err == 1) + goto skip_part_check; + + if (desc->version != 1) { + fsverity_err(inode, "Unrecognized descriptor version: %u", + desc->version); + return ERR_PTR(-EINVAL); + } + + if (memchr_inv(desc->__reserved, 0, sizeof(desc->__reserved))) { + fsverity_err(inode, "Reserved bits set in descriptor"); + return ERR_PTR(-EINVAL); + } + + if (desc->salt_size > sizeof(desc->salt)) { + fsverity_err(inode, "Invalid salt_size: %u", desc->salt_size); + return ERR_PTR(-EINVAL); + } + + if (le64_to_cpu(desc->data_size) != inode->i_size) { + fsverity_err(inode, + "Wrong data_size: %llu (desc) != %lld (inode)", + le64_to_cpu(desc->data_size), inode->i_size); + return ERR_PTR(-EINVAL); + } + +skip_part_check: vi = kmem_cache_zalloc(fsverity_info_cachep, GFP_KERNEL); if (!vi) return ERR_PTR(-ENOMEM); vi->inode = inode; +#ifdef CONFIG_SECURITY_CODE_SIGN + vi->verified_data_size = le64_to_cpu(desc->data_size); +#endif + err = fsverity_init_merkle_tree_params(&vi->tree_params, inode, desc->hash_algorithm, desc->log_blocksize, - desc->salt, desc->salt_size); + desc->salt, desc->salt_size, + le64_to_cpu(desc->data_size)); if (err) { fsverity_err(inode, "Error %d initializing Merkle tree parameters", diff --git a/fs/verity/signature.c b/fs/verity/signature.c index 143a530a80088bc62514b65ad3e21718ca819077..75c9ad917c0d35da133549aa6e9408debdcc6a31 100644 --- a/fs/verity/signature.c +++ b/fs/verity/signature.c @@ -11,6 +11,7 @@ #include #include #include +#include /* * /proc/sys/fs/verity/require_signatures @@ -26,6 +27,45 @@ static int fsverity_require_signatures; */ static struct key *fsverity_keyring; +static struct key *fsverity_keyring; + +#ifdef CONFIG_SECURITY_CODE_SIGN + +void fsverity_set_cert_type(struct fsverity_info *vi, + int cert_type) +{ + vi->cert_type = cert_type; +} + +int fsverity_get_cert_type(const struct inode *inode) +{ + return fsverity_get_info(inode)->cert_type; +} + +#else /* !CONFIG_SECURITY_CODE_SIGN */ + +static void inline fsverity_set_cert_type(struct fsverity_info *verity_info, + int cert_type) +{ +} + +#endif + +static inline int fsverity_verify_certchain(struct fsverity_info *vi, + const void *raw_pkcs7, size_t pkcs7_len) +{ + int ret = 0; + + CALL_HCK_LITE_HOOK(code_sign_verify_certchain_lhck, + raw_pkcs7, pkcs7_len, &vi->fcs_info, &ret); + if (ret > 0) { + fsverity_set_cert_type(vi, ret); + ret = 0; + } + + return ret; +} + /** * fsverity_verify_signature() - check a verity file's signature * @vi: the file's fsverity_info diff --git a/fs/verity/verify.c b/fs/verity/verify.c index 0adb970f4e7368a3c138edfe66a46a714d47b3d0..7cf7b0d1586a86332bd52422a0c1cbc54a7e6256 100644 --- a/fs/verity/verify.c +++ b/fs/verity/verify.c @@ -103,6 +103,13 @@ static bool verify_page(struct inode *inode, const struct fsverity_info *vi, pr_debug_ratelimited("Verifying data page %lu...\n", index); +#ifdef CONFIG_SECURITY_CODE_SIGN + if (index >= DIV_ROUND_UP(vi->verified_data_size, PAGE_SIZE)) { + pr_debug_ratelimited("Data out of verity range %lu\n", + vi->verified_data_size >> PAGE_SHIFT); + return true; + } +#endif /* * Starting at the leaf level, ascend the tree saving hash pages along * the way until we find a verified hash page, indicated by PageChecked; @@ -264,6 +271,22 @@ void fsverity_verify_bio(struct bio *bio) EXPORT_SYMBOL_GPL(fsverity_verify_bio); #endif /* CONFIG_BLOCK */ +/** + * fsverity_get_verified_data_size() - get verified data size of a verity file + * @inode: the file's inode + * + * Return: verified data size + */ +u64 fsverity_get_verified_data_size(const struct inode *inode) +{ +#ifdef CONFIG_SECURITY_CODE_SIGN + return fsverity_get_info(inode)->verified_data_size; +#else + return inode->i_size; +#endif +} + + /** * fsverity_enqueue_verify_work() - enqueue work on the fs-verity workqueue * @work: the work to enqueue diff --git a/include/linux/code_sign.h b/include/linux/code_sign.h new file mode 100644 index 0000000000000000000000000000000000000000..c44f15fc58066a99d2e3f5c9bafce20ee812dda1 --- /dev/null +++ b/include/linux/code_sign.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef LINUX_INCLUDE_CODE_SIGN_H +#define LINUX_INCLUDE_CODE_SIGN_H + +#include + +/* + * Merkle tree properties. The file measurement is the hash of this structure + * excluding the signature and with the sig_size field set to 0, while version + * is replaced by code sign version. + */ +struct code_sign_descriptor { + __u8 version; /* must be 1 */ + __u8 hash_algorithm; /* Merkle tree hash algorithm */ + __u8 log_blocksize; /* log2 of size of data and tree blocks */ + __u8 salt_size; /* size of salt in bytes; 0 if none */ + __le32 sig_size; /* size of signature in bytes; 0 if none */ + __le64 data_size; /* size of file the Merkle tree is built over */ + __u8 root_hash[64]; /* Merkle tree root hash */ + __u8 salt[32]; /* salt prepended to each hashed block */ + __u32 flags; + __u32 __reserved1; /* must be 0 */ + __u64 tree_offset; /* merkle tree offset in file */ + __u8 __reserved2[127]; /* must be 0's */ + __u8 cs_version; /* code sign version */ + __u8 signature[]; /* optional PKCS#7 signature */ +}; + +enum { + RELEASE_CODE_START = 0x0, + RELEASE_PLATFORM_CODE, + RELEASE_AUTHED_CODE, + RELEASE_DEVELOPER_CODE, + RELEASE_BLOCK_CODE, + RELEASE_CODE_END, + + DEBUG_CODE_START = 0x100, + DEBUG_PLATFORM_CODE, + DEBUG_AUTHED_CODE, + DEBUG_DEVELOPER_CODE, + DEBUG_BLOCK_CODE, + DEBUG_DEBUG_CODE, + DEBUG_CODE_END, + + MAY_LOCAL_CODE = 0x201, +}; + +#define FLAG_INSIDE_TREE (1 << 0) /* Merkle tree in file */ +#define IS_INSIDE_TREE(desc) ((desc)->flags & FLAG_INSIDE_TREE) + +#define CONST_CAST_CODE_SIGN_DESC(desc) ((const struct code_sign_descriptor *)(desc)) +#define CAST_CODE_SIGN_DESC(desc) ((struct code_sign_descriptor *)(desc)) + +static inline u64 get_tree_offset_compact(const void *desc) +{ + return CONST_CAST_CODE_SIGN_DESC(desc)->tree_offset; +} + +static inline bool is_inside_tree_compact(const void *_desc) +{ + const struct code_sign_descriptor *desc = CONST_CAST_CODE_SIGN_DESC(_desc); + + return desc->cs_version && IS_INSIDE_TREE(desc); +} + +static inline int code_sign_check_descriptor_hook(const struct inode *inode, const void *desc) +{ + int ret = 0; + + CALL_HCK_LITE_HOOK(code_sign_check_descriptor_lhck, inode, desc, &ret); + return ret; +} + +static inline int code_sign_before_measurement_hook(void *desc) +{ + int ret = 0; + + CALL_HCK_LITE_HOOK(code_sign_before_measurement_lhck, desc, &ret); + return ret; +} + +static inline void code_sign_after_measurement_hook(void *desc, int version) +{ + CALL_HCK_LITE_HOOK(code_sign_after_measurement_lhck, desc, version); +} + +#endif /* LINUX_INCLUDE_CODE_SIGN_H */ \ No newline at end of file diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h index b568b3c7d095ea76edf6923ce316ff064dcad9a3..c773185e9b95cabdeae0a370326c6db1da0e6e62 100644 --- a/include/linux/fsverity.h +++ b/include/linux/fsverity.h @@ -127,6 +127,8 @@ static inline struct fsverity_info *fsverity_get_info(const struct inode *inode) /* enable.c */ int fsverity_ioctl_enable(struct file *filp, const void __user *arg); +int fsverity_enable_with_descriptor(struct file *filp, + void *desc, size_t desc_size); /* measure.c */ @@ -147,6 +149,7 @@ int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg); bool fsverity_verify_page(struct page *page); void fsverity_verify_bio(struct bio *bio); void fsverity_enqueue_verify_work(struct work_struct *work); +u64 fsverity_get_verified_data_size(const struct inode *inode); #else /* !CONFIG_FS_VERITY */ @@ -163,6 +166,12 @@ static inline int fsverity_ioctl_enable(struct file *filp, return -EOPNOTSUPP; } +static inline int fsverity_enable_with_descriptor(struct file *filp, + void *desc, size_t desc_size) +{ + return -EOPNOTSUPP; +} + /* measure.c */ static inline int fsverity_ioctl_measure(struct file *filp, void __user *arg) @@ -213,8 +222,36 @@ static inline void fsverity_enqueue_verify_work(struct work_struct *work) WARN_ON(1); } +static inline u64 fsverity_get_verified_data_size(const struct inode *inode) +{ + WARN_ON(1); + return inode->i_size; +} + #endif /* !CONFIG_FS_VERITY */ +#ifdef CONFIG_SECURITY_CODE_SIGN + +/* enable.c */ + +int fsverity_ioctl_enable_code_sign(struct file *filp, const void __user *uarg); + +int fsverity_get_cert_type(const struct inode *inode); + +#else /* !CONFIG_SECURITY_CODE_SIGN */ + +static inline int fsverity_ioctl_enable_code_sign(struct file *filp, const void __user *uarg) +{ + return -EOPNOTSUPP; +} + +static inline int fsverity_get_cert_type(const struct inode *inode) +{ + return 0; +} + +#endif /* !CONFIG_SECURITY_CODE_SIGN */ + /** * fsverity_active() - do reads from the inode need to go through fs-verity? * @inode: inode to check diff --git a/include/uapi/linux/fsverity.h b/include/uapi/linux/fsverity.h index 15384e22e331e8738cf05c971f00b3fe6fdfcbb6..6d7dd45744f637b352d7dbbb7d7bffd40bd10de5 100644 --- a/include/uapi/linux/fsverity.h +++ b/include/uapi/linux/fsverity.h @@ -100,4 +100,23 @@ struct fsverity_read_metadata_arg { #define FS_IOC_READ_VERITY_METADATA \ _IOWR('f', 135, struct fsverity_read_metadata_arg) +struct code_sign_enable_arg { + __u32 version; + __u32 hash_algorithm; + __u32 block_size; + __u32 salt_size; + __u64 salt_ptr; + __u32 sig_size; + __u32 __reserved1; + __u64 sig_ptr; + __u64 __reserved2[7]; + __u64 tree_offset; + __u64 root_hash_ptr; + __u64 data_size; + __u32 flags; + __u32 cs_version; +}; + +#define FS_IOC_ENABLE_CODE_SIGN _IOW('f', 200, struct code_sign_enable_arg) + #endif /* _UAPI_LINUX_FSVERITY_H */ diff --git a/scripts/kconfig/lexer.l b/scripts/kconfig/lexer.l index 312cbad2d34d41c494b6441268950a1a968f7141..b61b9c88a14160e8d9725f5d4b2a0c4509863c90 100644 --- a/scripts/kconfig/lexer.l +++ b/scripts/kconfig/lexer.l @@ -20,6 +20,15 @@ #define START_STRSIZE 16 +static const char *kconfig_white_list[] = { + "vendor/Kconfig", + "net/newip/Kconfig", + "security/xpm/Kconfig", + "drivers/auth_ctl/Kconfig", + "drivers/staging/ucollection/Kconfig", + "fs/code_sign/Kconfig", +}; + static struct { struct file *file; int lineno; diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 084757ff43906371bdbda842abaaa4f392aa489e..2d3a67fcb7afa2bcc42dbb620d1bff28acb55eba 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -254,6 +254,8 @@ struct security_class_mapping secclass_map[] = { { "integrity", "confidentiality", NULL } }, { "anon_inode", { COMMON_FILE_PERMS, NULL } }, + { "code_sign", + { "add_cert_chain", "remove_cert_chain", NULL } }, { NULL } };