Skip to content

Commit

Permalink
ubifs: Authenticate replayed journal
Browse files Browse the repository at this point in the history
Make sure that during replay all buds can be authenticated. To do
this we calculate the hash chain until we find an authentication
node and check the HMAC in that node against the current status
of the hash chain.

After a power cut it can happen that some nodes have been written, but
not yet the authentication node for them. These nodes have to be
discarded during replay.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: Richard Weinberger <richard@nod.at>
  • Loading branch information
saschahauer authored and richardweinberger committed Oct 23, 2018
1 parent 6f06d96 commit da8ef65
Showing 1 changed file with 144 additions and 2 deletions.
146 changes: 144 additions & 2 deletions fs/ubifs/replay.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

#include "ubifs.h"
#include <linux/list_sort.h>
#include <crypto/hash.h>
#include <crypto/algapi.h>

/**
* struct replay_entry - replay list entry.
Expand Down Expand Up @@ -531,6 +533,105 @@ static int is_last_bud(struct ubifs_info *c, struct ubifs_bud *bud)
return data == 0xFFFFFFFF;
}

/**
* authenticate_sleb - authenticate one scan LEB
* @c: UBIFS file-system description object
* @sleb: the scan LEB to authenticate
* @log_hash:
* @is_last: if true, this is is the last LEB
*
* This function iterates over the buds of a single LEB authenticating all buds
* with the authentication nodes on this LEB. Authentication nodes are written
* after some buds and contain a HMAC covering the authentication node itself
* and the buds between the last authentication node and the current
* authentication node. It can happen that the last buds cannot be authenticated
* because a powercut happened when some nodes were written but not the
* corresponding authentication node. This function returns the number of nodes
* that could be authenticated or a negative error code.
*/
static int authenticate_sleb(struct ubifs_info *c, struct ubifs_scan_leb *sleb,
struct shash_desc *log_hash, int is_last)
{
int n_not_auth = 0;
struct ubifs_scan_node *snod;
int n_nodes = 0;
int err;
u8 *hash, *hmac;

if (!ubifs_authenticated(c))
return sleb->nodes_cnt;

hash = kmalloc(crypto_shash_descsize(c->hash_tfm), GFP_NOFS);
hmac = kmalloc(c->hmac_desc_len, GFP_NOFS);
if (!hash || !hmac) {
err = -ENOMEM;
goto out;
}

list_for_each_entry(snod, &sleb->nodes, list) {

n_nodes++;

if (snod->type == UBIFS_AUTH_NODE) {
struct ubifs_auth_node *auth = snod->node;
SHASH_DESC_ON_STACK(hash_desc, c->hash_tfm);
SHASH_DESC_ON_STACK(hmac_desc, c->hmac_tfm);

hash_desc->tfm = c->hash_tfm;
hash_desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;

ubifs_shash_copy_state(c, log_hash, hash_desc);
err = crypto_shash_final(hash_desc, hash);
if (err)
goto out;

hmac_desc->tfm = c->hmac_tfm;
hmac_desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
err = crypto_shash_digest(hmac_desc, hash, c->hash_len,
hmac);
if (err)
goto out;

err = ubifs_check_hmac(c, auth->hmac, hmac);
if (err) {
err = -EPERM;
goto out;
}
n_not_auth = 0;
} else {
err = crypto_shash_update(log_hash, snod->node,
snod->len);
if (err)
goto out;
n_not_auth++;
}
}

/*
* A powercut can happen when some nodes were written, but not yet
* the corresponding authentication node. This may only happen on
* the last bud though.
*/
if (n_not_auth) {
if (is_last) {
dbg_mnt("%d unauthenticated nodes found on LEB %d, Ignoring them",
n_not_auth, sleb->lnum);
err = 0;
} else {
dbg_mnt("%d unauthenticated nodes found on non-last LEB %d",
n_not_auth, sleb->lnum);
err = -EPERM;
}
} else {
err = 0;
}
out:
kfree(hash);
kfree(hmac);

return err ? err : n_nodes - n_not_auth;
}

/**
* replay_bud - replay a bud logical eraseblock.
* @c: UBIFS file-system description object
Expand All @@ -544,6 +645,7 @@ static int replay_bud(struct ubifs_info *c, struct bud_entry *b)
{
int is_last = is_last_bud(c, b->bud);
int err = 0, used = 0, lnum = b->bud->lnum, offs = b->bud->start;
int n_nodes, n = 0;
struct ubifs_scan_leb *sleb;
struct ubifs_scan_node *snod;

Expand All @@ -563,6 +665,15 @@ static int replay_bud(struct ubifs_info *c, struct bud_entry *b)
if (IS_ERR(sleb))
return PTR_ERR(sleb);

n_nodes = authenticate_sleb(c, sleb, b->bud->log_hash, is_last);
if (n_nodes < 0) {
err = n_nodes;
goto out;
}

ubifs_shash_copy_state(c, b->bud->log_hash,
c->jheads[b->bud->jhead].log_hash);

/*
* The bud does not have to start from offset zero - the beginning of
* the 'lnum' LEB may contain previously committed data. One of the
Expand Down Expand Up @@ -676,6 +787,10 @@ static int replay_bud(struct ubifs_info *c, struct bud_entry *b)
}
if (err)
goto out;

n++;
if (n == n_nodes)
break;
}

ubifs_assert(c, ubifs_search_bud(c, lnum));
Expand Down Expand Up @@ -754,6 +869,7 @@ static int add_replay_bud(struct ubifs_info *c, int lnum, int offs, int jhead,
{
struct ubifs_bud *bud;
struct bud_entry *b;
int err;

dbg_mnt("add replay bud LEB %d:%d, head %d", lnum, offs, jhead);

Expand All @@ -763,20 +879,33 @@ static int add_replay_bud(struct ubifs_info *c, int lnum, int offs, int jhead,

b = kmalloc(sizeof(struct bud_entry), GFP_KERNEL);
if (!b) {
kfree(bud);
return -ENOMEM;
err = -ENOMEM;
goto out;
}

bud->lnum = lnum;
bud->start = offs;
bud->jhead = jhead;
bud->log_hash = ubifs_hash_get_desc(c);
if (IS_ERR(bud->log_hash)) {
err = PTR_ERR(bud->log_hash);
goto out;
}

ubifs_shash_copy_state(c, c->log_hash, bud->log_hash);

ubifs_add_bud(c, bud);

b->bud = bud;
b->sqnum = sqnum;
list_add_tail(&b->list, &c->replay_buds);

return 0;
out:
kfree(bud);
kfree(b);

return err;
}

/**
Expand Down Expand Up @@ -882,6 +1011,14 @@ static int replay_log_leb(struct ubifs_info *c, int lnum, int offs, void *sbuf)

c->cs_sqnum = le64_to_cpu(node->ch.sqnum);
dbg_mnt("commit start sqnum %llu", c->cs_sqnum);

err = ubifs_shash_init(c, c->log_hash);
if (err)
goto out;

err = ubifs_shash_update(c, c->log_hash, node, UBIFS_CS_NODE_SZ);
if (err < 0)
goto out;
}

if (snod->sqnum < c->cs_sqnum) {
Expand Down Expand Up @@ -929,6 +1066,11 @@ static int replay_log_leb(struct ubifs_info *c, int lnum, int offs, void *sbuf)
if (err)
goto out_dump;

err = ubifs_shash_update(c, c->log_hash, ref,
UBIFS_REF_NODE_SZ);
if (err)
goto out;

err = add_replay_bud(c, le32_to_cpu(ref->lnum),
le32_to_cpu(ref->offs),
le32_to_cpu(ref->jhead),
Expand Down

0 comments on commit da8ef65

Please sign in to comment.