Skip to content

Commit

Permalink
ubifs: support offline signed images
Browse files Browse the repository at this point in the history
HMACs can only be generated on the system the UBIFS image is running on.
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@pengutronix.de>
Signed-off-by: Richard Weinberger <richard@nod.at>
  • Loading branch information
Sascha Hauer authored and Richard Weinberger committed Jul 8, 2019
1 parent 8ba0a2a commit 817aa09
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 44 deletions.
3 changes: 2 additions & 1 deletion fs/ubifs/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ config UBIFS_FS_SECURITY

config UBIFS_FS_AUTHENTICATION
bool "UBIFS authentication support"
depends on KEYS
select 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.
Expand Down
86 changes: 86 additions & 0 deletions fs/ubifs/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -198,6 +200,77 @@ 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;

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_first_entry(&sleb->nodes, struct ubifs_scan_node, list);

if (snod->type != UBIFS_SIG_NODE) {
ubifs_err(c, "Signature node is of wrong type");
err = -EINVAL;
goto out_destroy;
}

signode = snod->node;

if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node)) {
ubifs_err(c, "invalid signature len %d", le32_to_cpu(signode->len));
err = -EINVAL;
goto out_destroy;
}

if (le32_to_cpu(signode->type) != UBIFS_SIGNATURE_TYPE_PKCS7) {
ubifs_err(c, "Signature type %d is not supported\n",
le32_to_cpu(signode->type));
err = -EINVAL;
goto out_destroy;
}

err = verify_pkcs7_signature(sup, sizeof(struct ubifs_sb_node),
signode->sig, le32_to_cpu(signode->len),
NULL, VERIFYING_UNSPECIFIED_SIGNATURE,
NULL, NULL);

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

out_destroy:
ubifs_scan_destroy(sleb);

return err;
}

/**
* ubifs_init_authentication - initialize UBIFS authentication support
* @c: UBIFS file-system description object
Expand Down Expand Up @@ -478,3 +551,16 @@ 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)
{
return !memchr_inv(hmac, 0, c->hmac_desc_len);
}
53 changes: 47 additions & 6 deletions fs/ubifs/master.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,39 @@ 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;

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
Expand Down Expand Up @@ -102,14 +135,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:
Expand Down
52 changes: 27 additions & 25 deletions fs/ubifs/sb.c
Original file line number Diff line number Diff line change
Expand Up @@ -578,17 +578,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);

Expand Down Expand Up @@ -744,21 +753,16 @@ int ubifs_read_superblock(struct ubifs_info *c)
}

/* Automatically increase file system size to the maximum size */
c->old_leb_cnt = c->leb_cnt;
if (c->leb_cnt < c->vi.size && c->leb_cnt < c->max_leb_cnt) {
int old_leb_cnt = c->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",
old_leb_cnt, c->leb_cnt);
}

c->log_bytes = (long long)c->log_lebs * c->leb_size;
Expand Down Expand Up @@ -916,9 +920,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;
Expand Down
41 changes: 32 additions & 9 deletions fs/ubifs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,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;
Expand Down Expand Up @@ -1359,6 +1361,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;
Expand Down Expand Up @@ -1643,15 +1665,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);
Expand Down Expand Up @@ -1683,6 +1696,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;
Expand Down
Loading

0 comments on commit 817aa09

Please sign in to comment.