Re: [PATCH] ubifs: support offline signed images

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



----- Ursprüngliche Mail -----
> Von: "Sascha Hauer" <s.hauer@xxxxxxxxxxxxxx>
> An: "linux-mtd" <linux-mtd@xxxxxxxxxxxxxxxxxxx>
> CC: "richard" <richard@xxxxxx>, kernel@xxxxxxxxxxxxxx, "Sascha Hauer" <s.hauer@xxxxxxxxxxxxxx>
> Gesendet: Montag, 1. April 2019 16:30:01
> Betreff: [PATCH] ubifs: support offline signed images

> HMACs can only be generated on the system the UBIFS image is running on.

Because at mkfs.ubifs time you don't have the key which might be hardware
specific, right?

> To support offline signed images we add a PKCS#7 signature to the UBIFS
> image which can be created by mkfs.ubifs.
> 
> Both the master node and the superblock need to be authenticated, during
> normal runtime both are protected with HMACs. For offline signature
> support however only a single signature is desired. We add a signature
> covering the superblock node directly behind it. To protect the master
> node a hash of the master node is added to the superblock which is used
> when the master node doesn't contain a HMAC.
> 
> Transition to a read/write filesystem is also supported. During
> transition first the master node is rewritten with a HMAC (implicitly,
> it is written anyway as the FS is marked dirty). Afterwards the
> superblock is rewritten with a HMAC. Once after the image has been
> mounted read/write it is HMAC only, the signature is no longer required
> or even present on the filesystem.
> 
> In an offline signed image the master node is authenticated by the
> superblock. In a transition to r/w we have to make sure that the master
> node is rewritten before the superblock node. In this case the master
> node gets a HMAC and its authenticity no longer depends on the
> superblock node. There are some cases in which the current code first
> writes the superblock node though, so with this patch writing of the
> superblock node is delayed until the master node is written.
> 
> Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx>
> ---
> fs/ubifs/Kconfig       |  1 +
> fs/ubifs/auth.c        | 81 ++++++++++++++++++++++++++++++++++++++++++
> fs/ubifs/master.c      | 54 ++++++++++++++++++++++++----
> fs/ubifs/sb.c          | 49 ++++++++++++-------------
> fs/ubifs/super.c       | 41 ++++++++++++++++-----
> fs/ubifs/ubifs-media.h | 21 ++++++++++-
> fs/ubifs/ubifs.h       |  4 +++
> 7 files changed, 211 insertions(+), 40 deletions(-)
> 
> diff --git a/fs/ubifs/Kconfig b/fs/ubifs/Kconfig
> index 9da2f135121b..3893668d5dae 100644
> --- a/fs/ubifs/Kconfig
> +++ b/fs/ubifs/Kconfig
> @@ -78,6 +78,7 @@ config UBIFS_FS_AUTHENTICATION
> 	bool "UBIFS authentication support"
> 	depends on KEYS
> 	select CRYPTO_HMAC
> +	select SYSTEM_DATA_VERIFICATION
> 	help
> 	  Enable authentication support for UBIFS. This feature offers protection
> 	  against offline changes for both data and metadata of the filesystem.
> diff --git a/fs/ubifs/auth.c b/fs/ubifs/auth.c
> index 5bf5fd08879e..cff5820ee54b 100644
> --- a/fs/ubifs/auth.c
> +++ b/fs/ubifs/auth.c
> @@ -10,10 +10,12 @@
>  */
> 
> #include <linux/crypto.h>
> +#include <linux/verification.h>
> #include <crypto/hash.h>
> #include <crypto/sha.h>
> #include <crypto/algapi.h>
> #include <keys/user-type.h>
> +#include <keys/asymmetric-type.h>
> 
> #include "ubifs.h"
> 
> @@ -217,6 +219,66 @@ int __ubifs_node_check_hash(const struct ubifs_info *c,
> const void *node,
> 	return 0;
> }
> 
> +/**
> + * ubifs_sb_verify_signature - verify the signature of a superblock
> + * @c: UBIFS file-system description object
> + * @sup: The superblock node
> + *
> + * To support offline signed images the superblock can be signed with a
> + * PKCS#7 signature. The signature is placed directly behind the superblock
> + * node in an ubifs_sig_node.
> + *
> + * Returns 0 when the signature can be successfully verified or a negative
> + * error code if not.
> + */
> +int ubifs_sb_verify_signature(struct ubifs_info *c,
> +			      const struct ubifs_sb_node *sup)
> +{
> +	int err;
> +	struct ubifs_scan_leb *sleb;
> +	struct ubifs_scan_node *snod;
> +	const struct ubifs_sig_node *signode;
> +
> +	err = ubifs_leb_read(c, UBIFS_SB_LNUM, c->sbuf, 0, c->leb_size, 1);

This line looks wrong here, leftover from old code? :)

> +	sleb = ubifs_scan(c, UBIFS_SB_LNUM, UBIFS_SB_NODE_SZ, c->sbuf, 0);
> +	if (IS_ERR(sleb)) {
> +		err = PTR_ERR(sleb);
> +		return err;
> +	}
> +
> +	if (sleb->nodes_cnt == 0) {
> +		ubifs_err(c, "Unable to find signature node");
> +		err = -EINVAL;
> +		goto out_destroy;
> +	}
> +
> +	snod = list_entry(sleb->nodes.next, struct ubifs_scan_node, list);

list_first_entry()?

> +	if (snod->type != UBIFS_SIG_NODE) {
> +		ubifs_err(c, "Signature node is of wrong type");
> +		err = -EINVAL;
> +		goto out_destroy;
> +	}
> +
> +	signode = snod->node;
> +
> +	err = verify_pkcs7_signature(sup, sizeof(struct ubifs_sb_node),
> +				     signode->sig, le32_to_cpu(signode->len),

signode->len is not verified, please embed a check on the length.

> +				     NULL, VERIFYING_UNSPECIFIED_SIGNATURE,
> +				     NULL, NULL);
> +
> +	if (err)
> +		ubifs_err(c, "Failed to verify signature");
> +	else
> +		ubifs_msg(c, "Successfully verified super block signature");

While this is good news, is it really worth telling the user every time?

> +out_destroy:
> +	ubifs_scan_destroy(sleb);
> +
> +	return err;
> +}
> +
> /**
>  * ubifs_init_authentication - initialize UBIFS authentication support
>  * @c: UBIFS file-system description object
> @@ -499,3 +561,22 @@ int ubifs_hmac_wkm(struct ubifs_info *c, u8 *hmac)
> 		return err;
> 	return 0;
> }
> +
> +/*
> + * ubifs_hmac_zero - test if a HMAC is zero
> + * @c: UBIFS file-system description object
> + * @hmac: the HMAC to test
> + *
> + * This function tests if a HMAC is zero and returns true if it is
> + * and false otherwise.
> + */
> +bool ubifs_hmac_zero(struct ubifs_info *c, const u8 *hmac)
> +{
> +	int i;
> +
> +	for (i = 0; i < c->hmac_desc_len; i++)
> +		if (hmac[i] != 0)
> +			return false;
> +
> +	return true;
> +}

Isn't there an helper available?
Maybe based on memcmp()?

> diff --git a/fs/ubifs/master.c b/fs/ubifs/master.c
> index 5ea51bbd14c7..5655414a76a7 100644
> --- a/fs/ubifs/master.c
> +++ b/fs/ubifs/master.c
> @@ -60,6 +60,40 @@ int ubifs_compare_master_node(struct ubifs_info *c, void *m1,
> void *m2)
> 	return 0;
> }
> 
> +/* mst_node_check_hash - Check hash of a master node
> + * @c: UBIFS file-system description object
> + * @mst: The master node
> + * @expected: The expected hash of the master node
> + *
> + * This checks the hash of a master node against a given expected hash.
> + * Note that we have two master nodes on a UBIFS image which have different
> + * sequence numbers and consequently different CRCs. To be able to match
> + * both master nodes we exclude the common node header containing the sequence
> + * number and CRC from the hash.
> + *
> + * Returns 0 if the hashes are equal, a negative error code otherwise.
> + */
> +static int mst_node_check_hash(const struct ubifs_info *c,
> +			       const struct ubifs_mst_node *mst,
> +			       const u8 *expected)
> +{
> +	u8 calc[UBIFS_MAX_HASH_LEN];
> +	const void *node = mst;
> +
> +	SHASH_DESC_ON_STACK(shash, c->hash_tfm);
> +
> +	shash->tfm = c->hash_tfm;
> +	shash->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
> +
> +	crypto_shash_digest(shash, node + sizeof(struct ubifs_ch),
> +			    UBIFS_MST_NODE_SZ - sizeof(struct ubifs_ch), calc);
> +
> +	if (ubifs_check_hash(c, expected, calc))
> +		return -EPERM;
> +
> +	return 0;
> +}
> +
> /**
>  * scan_for_master - search the valid master node.
>  * @c: UBIFS file-system description object
> @@ -114,14 +148,22 @@ static int scan_for_master(struct ubifs_info *c)
> 	if (!ubifs_authenticated(c))
> 		return 0;
> 
> -	err = ubifs_node_verify_hmac(c, c->mst_node,
> -				     sizeof(struct ubifs_mst_node),
> -				     offsetof(struct ubifs_mst_node, hmac));
> -	if (err) {
> -		ubifs_err(c, "Failed to verify master node HMAC");
> -		return -EPERM;
> +	if (ubifs_hmac_zero(c, c->mst_node->hmac)) {
> +		err = mst_node_check_hash(c, c->mst_node,
> +					  c->sup_node->hash_mst);
> +		if (err)
> +			ubifs_err(c, "Failed to verify master node hash");
> +	} else {
> +		err = ubifs_node_verify_hmac(c, c->mst_node,
> +					sizeof(struct ubifs_mst_node),
> +					offsetof(struct ubifs_mst_node, hmac));
> +		if (err)
> +			ubifs_err(c, "Failed to verify master node HMAC");
> 	}
> 
> +	if (err)
> +		return -EPERM;
> +
> 	return 0;
> 
> out:
> diff --git a/fs/ubifs/sb.c b/fs/ubifs/sb.c
> index 67fac1e8adfb..3dd195dbd852 100644
> --- a/fs/ubifs/sb.c
> +++ b/fs/ubifs/sb.c
> @@ -590,17 +590,26 @@ static int authenticate_sb_node(struct ubifs_info *c,
> 		return -EINVAL;
> 	}
> 
> -	err = ubifs_hmac_wkm(c, hmac_wkm);
> -	if (err)
> -		return err;
> -
> -	if (ubifs_check_hmac(c, hmac_wkm, sup->hmac_wkm)) {
> -		ubifs_err(c, "provided key does not fit");
> -		return -ENOKEY;
> +	/*
> +	 * The super block node can either be authenticated by a HMAC or
> +	 * by a signature in a ubifs_sig_node directly following the
> +	 * super block node to support offline image creation.
> +	 */
> +	if (ubifs_hmac_zero(c, sup->hmac)) {
> +		err = ubifs_sb_verify_signature(c, sup);
> +	} else {
> +		err = ubifs_hmac_wkm(c, hmac_wkm);
> +		if (err)
> +			return err;
> +		if (ubifs_check_hmac(c, hmac_wkm, sup->hmac_wkm)) {
> +			ubifs_err(c, "provided key does not fit");
> +			return -ENOKEY;
> +		}
> +		err = ubifs_node_verify_hmac(c, sup, sizeof(*sup),
> +					     offsetof(struct ubifs_sb_node,
> +						      hmac));
> 	}
> 
> -	err = ubifs_node_verify_hmac(c, sup, sizeof(*sup),
> -				     offsetof(struct ubifs_sb_node, hmac));
> 	if (err)
> 		ubifs_err(c, "Failed to authenticate superblock: %d", err);
> 
> @@ -761,18 +770,12 @@ int ubifs_read_superblock(struct ubifs_info *c)
> 	c->old_leb_cnt = c->leb_cnt;
> 	if (c->leb_cnt < c->vi.size && c->leb_cnt < c->max_leb_cnt) {
> 		c->leb_cnt = min_t(int, c->max_leb_cnt, c->vi.size);
> -		if (c->ro_mount)
> -			dbg_mnt("Auto resizing (ro) from %d LEBs to %d LEBs",
> -				c->old_leb_cnt,	c->leb_cnt);
> -		else {
> -			dbg_mnt("Auto resizing (sb) from %d LEBs to %d LEBs",
> -				c->old_leb_cnt, c->leb_cnt);
> -			sup->leb_cnt = cpu_to_le32(c->leb_cnt);
> -			err = ubifs_write_sb_node(c, sup);
> -			if (err)
> -				goto out;
> -			c->old_leb_cnt = c->leb_cnt;
> -		}
> +		sup->leb_cnt = cpu_to_le32(c->leb_cnt);
> +
> +		c->superblock_need_write = 1;
> +
> +		dbg_mnt("Auto resizing from %d LEBs to %d LEBs",
> +			c->old_leb_cnt,	c->leb_cnt);

Hmm, I don't fully understand the logic here.
What happens to c->old_leb_cnt and the ro_mount case?

> 	}
> 
> 	c->log_bytes = (long long)c->log_lebs * c->leb_size;
> @@ -930,9 +933,7 @@ int ubifs_fixup_free_space(struct ubifs_info *c)
> 	c->space_fixup = 0;
> 	sup->flags &= cpu_to_le32(~UBIFS_FLG_SPACE_FIXUP);
> 
> -	err = ubifs_write_sb_node(c, sup);
> -	if (err)
> -		return err;
> +	c->superblock_need_write = 1;
> 
> 	ubifs_msg(c, "free space fixup complete");
> 	return err;
> diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c
> index 8dc2818fdd84..19011e3c6ec7 100644
> --- a/fs/ubifs/super.c
> +++ b/fs/ubifs/super.c
> @@ -582,6 +582,8 @@ static int init_constants_early(struct ubifs_info *c)
> 	c->ranges[UBIFS_AUTH_NODE].min_len = UBIFS_AUTH_NODE_SZ;
> 	c->ranges[UBIFS_AUTH_NODE].max_len = UBIFS_AUTH_NODE_SZ +
> 				UBIFS_MAX_HMAC_LEN;
> +	c->ranges[UBIFS_SIG_NODE].min_len = UBIFS_SIG_NODE_SZ;
> +	c->ranges[UBIFS_SIG_NODE].max_len = c->leb_size - UBIFS_SB_NODE_SZ;
> 
> 	c->ranges[UBIFS_INO_NODE].min_len  = UBIFS_INO_NODE_SZ;
> 	c->ranges[UBIFS_INO_NODE].max_len  = UBIFS_MAX_INO_NODE_SZ;
> @@ -1376,6 +1378,26 @@ static int mount_ubifs(struct ubifs_info *c)
> 			goto out_lpt;
> 	}
> 
> +	/*
> +	 * Handle offline signed images: Now that the master node is
> +	 * written and its validation no longer depends on the hash
> +	 * in the superblock, we can update the offline signed
> +	 * superblock with a HMAC version,
> +	 */
> +	if (ubifs_authenticated(c) && ubifs_hmac_zero(c, c->sup_node->hmac)) {
> +		err = ubifs_hmac_wkm(c, c->sup_node->hmac_wkm);
> +		if (err)
> +			goto out_lpt;
> +		c->superblock_need_write = 1;
> +	}
> +
> +	if (!c->ro_mount && c->superblock_need_write) {
> +		err = ubifs_write_sb_node(c, c->sup_node);
> +		if (err)
> +			goto out_lpt;
> +		c->superblock_need_write = 0;
> +	}
> +
> 	err = dbg_check_idx_size(c, c->bi.old_idx_sz);
> 	if (err)
> 		goto out_lpt;
> @@ -1658,15 +1680,6 @@ static int ubifs_remount_rw(struct ubifs_info *c)
> 	if (err)
> 		goto out;
> 
> -	if (c->old_leb_cnt != c->leb_cnt) {
> -		struct ubifs_sb_node *sup = c->sup_node;
> -
> -		sup->leb_cnt = cpu_to_le32(c->leb_cnt);
> -		err = ubifs_write_sb_node(c, sup);
> -		if (err)
> -			goto out;
> -	}
> -
> 	if (c->need_recovery) {
> 		ubifs_msg(c, "completing deferred recovery");
> 		err = ubifs_write_rcvrd_mst_node(c);
> @@ -1698,6 +1711,16 @@ static int ubifs_remount_rw(struct ubifs_info *c)
> 			goto out;
> 	}
> 
> +	if (c->superblock_need_write) {
> +		struct ubifs_sb_node *sup = c->sup_node;
> +
> +		err = ubifs_write_sb_node(c, sup);
> +		if (err)
> +			goto out;
> +
> +		c->superblock_need_write = 0;
> +	}
> +
> 	c->ileb_buf = vmalloc(c->leb_size);
> 	if (!c->ileb_buf) {
> 		err = -ENOMEM;
> diff --git a/fs/ubifs/ubifs-media.h b/fs/ubifs/ubifs-media.h
> index 8b7c1844014f..3a73eb59859f 100644
> --- a/fs/ubifs/ubifs-media.h
> +++ b/fs/ubifs/ubifs-media.h
> @@ -287,6 +287,8 @@ enum {
> #define UBIFS_CS_NODE_SZ   sizeof(struct ubifs_cs_node)
> #define UBIFS_ORPH_NODE_SZ sizeof(struct ubifs_orph_node)
> #define UBIFS_AUTH_NODE_SZ sizeof(struct ubifs_auth_node)
> +#define UBIFS_SIG_NODE_SZ  sizeof(struct ubifs_sig_node)
> +
> /* Extended attribute entry nodes are identical to directory entry nodes */
> #define UBIFS_XENT_NODE_SZ UBIFS_DENT_NODE_SZ
> /* Only this does not have to be multiple of 8 bytes */
> @@ -373,6 +375,7 @@ enum {
>  * UBIFS_CS_NODE: commit start node
>  * UBIFS_ORPH_NODE: orphan node
>  * UBIFS_AUTH_NODE: authentication node
> + * UBIFS_SIG_NODE: signature node
>  * UBIFS_NODE_TYPES_CNT: count of supported node types
>  *
>  * Note, we index arrays by these numbers, so keep them low and contiguous.
> @@ -393,6 +396,7 @@ enum {
> 	UBIFS_CS_NODE,
> 	UBIFS_ORPH_NODE,
> 	UBIFS_AUTH_NODE,
> +	UBIFS_SIG_NODE,
> 	UBIFS_NODE_TYPES_CNT,
> };
> 
> @@ -650,6 +654,8 @@ struct ubifs_pad_node {
>  * @hmac_wkm: HMAC of a well known message (the string "UBIFS") as a convenience
>  *            to the user to check if the correct key is passed.
>  * @hash_algo: The hash algo used for this filesystem (one of enum hash_algo)
> + * @hash_mst: hash of the master node, only valid for signed images in which
> the
> + *            master node does not contain a hmac
>  */
> struct ubifs_sb_node {
> 	struct ubifs_ch ch;
> @@ -680,7 +686,8 @@ struct ubifs_sb_node {
> 	__u8 hmac[UBIFS_MAX_HMAC_LEN];
> 	__u8 hmac_wkm[UBIFS_MAX_HMAC_LEN];
> 	__le16 hash_algo;
> -	__u8 padding2[3838];
> +	__u8 hash_mst[UBIFS_MAX_HASH_LEN];
> +	__u8 padding2[3774];
> } __packed;
> 
> /**
> @@ -782,6 +789,18 @@ struct ubifs_auth_node {
> 	__u8 hmac[];
> } __packed;
> 
> +/**
> + * struct ubifs_sig_node - node for signing other nodes
> + * @ch: common header
> + * @len: The length of the signature data
> + * @sig: The signature data
> + */
> +struct ubifs_sig_node {
> +	struct ubifs_ch ch;
> +	__le32 len;
> +	__u8 sig[];
> +} __packed;
> +

Can we please have a version or type field?
Just in case we want to support more than PKCS#7 at some point.
This new node is not used at lot, so we can waste a little space...

Did you check, what is the typical length of a signature?
Maybe adding more padding fields is worth it.

> /**
>  * struct ubifs_branch - key/reference/length branch
>  * @lnum: LEB number of the target node
> diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h
> index 1ae12900e01d..56afa87dd3ae 100644
> --- a/fs/ubifs/ubifs.h
> +++ b/fs/ubifs/ubifs.h
> @@ -1304,6 +1304,7 @@ struct ubifs_info {
> 	unsigned int rw_incompat:1;
> 	unsigned int assert_action:2;
> 	unsigned int authenticated:1;
> +	unsigned int superblock_need_write:1;
> 
> 	struct mutex tnc_mutex;
> 	struct ubifs_zbranch zroot;
> @@ -1689,6 +1690,9 @@ static inline int ubifs_auth_node_sz(const struct
> ubifs_info *c)
> 	else
> 		return 0;
> }
> +int ubifs_sb_verify_signature(struct ubifs_info *c,
> +			      const struct ubifs_sb_node *sup);
> +bool ubifs_hmac_zero(struct ubifs_info *c, const u8 *hmac);
> 
> int ubifs_hmac_wkm(struct ubifs_info *c, u8 *hmac);
> 
> --
> 2.20.1

Thanks,
//richard

______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/




[Index of Archives]     [LARTC]     [Bugtraq]     [Yosemite Forum]     [Photo]

  Powered by Linux