ubifs: Authenticate replayed journal
authorSascha Hauer <s.hauer@pengutronix.de>
Fri, 7 Sep 2018 12:36:38 +0000 (14:36 +0200)
committerRichard Weinberger <richard@nod.at>
Tue, 23 Oct 2018 11:48:40 +0000 (13:48 +0200)
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>
fs/ubifs/replay.c

index db489d9..75f961c 100644 (file)
@@ -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.
@@ -532,6 +534,105 @@ static int is_last_bud(struct ubifs_info *c, struct ubifs_bud *bud)
 }
 
 /**
+ * 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
  * @b: bud entry which describes the bud
@@ -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;
 
@@ -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
@@ -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));
@@ -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);
 
@@ -763,13 +879,21 @@ 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;
@@ -777,6 +901,11 @@ static int add_replay_bud(struct ubifs_info *c, int lnum, int offs, int jhead,
        list_add_tail(&b->list, &c->replay_buds);
 
        return 0;
+out:
+       kfree(bud);
+       kfree(b);
+
+       return err;
 }
 
 /**
@@ -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) {
@@ -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),