fs/ntfs3: Check fields while reading
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Mon, 10 Oct 2022 10:15:33 +0000 (13:15 +0300)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Mon, 14 Nov 2022 16:50:47 +0000 (19:50 +0300)
Added new functions index_hdr_check and index_buf_check.
Now we check all stuff for correctness while reading from disk.
Also fixed bug with stale nfs data.

Reported-by: van fantasy <g1042620637@gmail.com>
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/index.c
fs/ntfs3/inode.c
fs/ntfs3/ntfs_fs.h
fs/ntfs3/run.c
fs/ntfs3/xattr.c

index 35369ae5c438c479a455e380bf49a2b2cb095511..51ab7595464031d1afb87d87aaba1add5a93142f 100644 (file)
@@ -605,11 +605,58 @@ static const struct NTFS_DE *hdr_insert_head(struct INDEX_HDR *hdr,
        return e;
 }
 
+/*
+ * index_hdr_check
+ *
+ * return true if INDEX_HDR is valid
+ */
+static bool index_hdr_check(const struct INDEX_HDR *hdr, u32 bytes)
+{
+       u32 end = le32_to_cpu(hdr->used);
+       u32 tot = le32_to_cpu(hdr->total);
+       u32 off = le32_to_cpu(hdr->de_off);
+
+       if (!IS_ALIGNED(off, 8) || tot > bytes || end > tot ||
+           off + sizeof(struct NTFS_DE) > end) {
+               /* incorrect index buffer. */
+               return false;
+       }
+
+       return true;
+}
+
+/*
+ * index_buf_check
+ *
+ * return true if INDEX_BUFFER seems is valid
+ */
+static bool index_buf_check(const struct INDEX_BUFFER *ib, u32 bytes,
+                           const CLST *vbn)
+{
+       const struct NTFS_RECORD_HEADER *rhdr = &ib->rhdr;
+       u16 fo = le16_to_cpu(rhdr->fix_off);
+       u16 fn = le16_to_cpu(rhdr->fix_num);
+
+       if (bytes <= offsetof(struct INDEX_BUFFER, ihdr) ||
+           rhdr->sign != NTFS_INDX_SIGNATURE ||
+           fo < sizeof(struct INDEX_BUFFER)
+           /* Check index buffer vbn. */
+           || (vbn && *vbn != le64_to_cpu(ib->vbn)) || (fo % sizeof(short)) ||
+           fo + fn * sizeof(short) >= bytes ||
+           fn != ((bytes >> SECTOR_SHIFT) + 1)) {
+               /* incorrect index buffer. */
+               return false;
+       }
+
+       return index_hdr_check(&ib->ihdr,
+                              bytes - offsetof(struct INDEX_BUFFER, ihdr));
+}
+
 void fnd_clear(struct ntfs_fnd *fnd)
 {
        int i;
 
-       for (i = 0; i < fnd->level; i++) {
+       for (i = fnd->level - 1; i >= 0; i--) {
                struct indx_node *n = fnd->nodes[i];
 
                if (!n)
@@ -819,9 +866,16 @@ int indx_init(struct ntfs_index *indx, struct ntfs_sb_info *sbi,
        u32 t32;
        const struct INDEX_ROOT *root = resident_data(attr);
 
+       t32 = le32_to_cpu(attr->res.data_size);
+       if (t32 <= offsetof(struct INDEX_ROOT, ihdr) ||
+           !index_hdr_check(&root->ihdr,
+                            t32 - offsetof(struct INDEX_ROOT, ihdr))) {
+               goto out;
+       }
+
        /* Check root fields. */
        if (!root->index_block_clst)
-               return -EINVAL;
+               goto out;
 
        indx->type = type;
        indx->idx2vbn_bits = __ffs(root->index_block_clst);
@@ -833,19 +887,19 @@ int indx_init(struct ntfs_index *indx, struct ntfs_sb_info *sbi,
        if (t32 < sbi->cluster_size) {
                /* Index record is smaller than a cluster, use 512 blocks. */
                if (t32 != root->index_block_clst * SECTOR_SIZE)
-                       return -EINVAL;
+                       goto out;
 
                /* Check alignment to a cluster. */
                if ((sbi->cluster_size >> SECTOR_SHIFT) &
                    (root->index_block_clst - 1)) {
-                       return -EINVAL;
+                       goto out;
                }
 
                indx->vbn2vbo_bits = SECTOR_SHIFT;
        } else {
                /* Index record must be a multiple of cluster size. */
                if (t32 != root->index_block_clst << sbi->cluster_bits)
-                       return -EINVAL;
+                       goto out;
 
                indx->vbn2vbo_bits = sbi->cluster_bits;
        }
@@ -853,7 +907,14 @@ int indx_init(struct ntfs_index *indx, struct ntfs_sb_info *sbi,
        init_rwsem(&indx->run_lock);
 
        indx->cmp = get_cmp_func(root);
-       return indx->cmp ? 0 : -EINVAL;
+       if (!indx->cmp)
+               goto out;
+
+       return 0;
+
+out:
+       ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
+       return -EINVAL;
 }
 
 static struct indx_node *indx_new(struct ntfs_index *indx,
@@ -1011,6 +1072,13 @@ int indx_read(struct ntfs_index *indx, struct ntfs_inode *ni, CLST vbn,
                goto out;
 
 ok:
+       if (!index_buf_check(ib, bytes, &vbn)) {
+               ntfs_inode_err(&ni->vfs_inode, "directory corrupted");
+               ntfs_set_state(ni->mi.sbi, NTFS_DIRTY_ERROR);
+               err = -EINVAL;
+               goto out;
+       }
+
        if (err == -E_NTFS_FIXUP) {
                ntfs_write_bh(ni->mi.sbi, &ib->rhdr, &in->nb, 0);
                err = 0;
@@ -1601,9 +1669,9 @@ static int indx_insert_into_root(struct ntfs_index *indx, struct ntfs_inode *ni,
 
        if (err) {
                /* Restore root. */
-               if (mi_resize_attr(mi, attr, -ds_root))
+               if (mi_resize_attr(mi, attr, -ds_root)) {
                        memcpy(attr, a_root, asize);
-               else {
+               else {
                        /* Bug? */
                        ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
                }
index 1afe6246aa9355bcbfd7959aee5fa7ec37143cb6..31bc94f879409cc4fb4ac5d8c0438fd324dec4c0 100644 (file)
@@ -81,7 +81,7 @@ static struct inode *ntfs_read_mft(struct inode *inode,
                         le16_to_cpu(ref->seq), le16_to_cpu(rec->seq));
                goto out;
        } else if (!is_rec_inuse(rec)) {
-               err = -EINVAL;
+               err = -ESTALE;
                ntfs_err(sb, "Inode r=%x is not in use!", (u32)ino);
                goto out;
        }
@@ -92,8 +92,10 @@ static struct inode *ntfs_read_mft(struct inode *inode,
                goto out;
        }
 
-       if (!is_rec_base(rec))
-               goto Ok;
+       if (!is_rec_base(rec)) {
+               err = -EINVAL;
+               goto out;
+       }
 
        /* Record should contain $I30 root. */
        is_dir = rec->flags & RECORD_FLAG_DIR;
@@ -466,7 +468,6 @@ end_enum:
                inode->i_flags |= S_NOSEC;
        }
 
-Ok:
        if (ino == MFT_REC_MFT && !sb->s_root)
                sbi->mft.ni = NULL;
 
@@ -520,6 +521,9 @@ struct inode *ntfs_iget5(struct super_block *sb, const struct MFT_REF *ref,
                _ntfs_bad_inode(inode);
        }
 
+       if (IS_ERR(inode) && name)
+               ntfs_set_state(sb->s_fs_info, NTFS_DIRTY_ERROR);
+
        return inode;
 }
 
@@ -1660,10 +1664,8 @@ out6:
                ntfs_remove_reparse(sbi, IO_REPARSE_TAG_SYMLINK, &new_de->ref);
 
 out5:
-       if (S_ISDIR(mode) || run_is_empty(&ni->file.run))
-               goto out4;
-
-       run_deallocate(sbi, &ni->file.run, false);
+       if (!S_ISDIR(mode))
+               run_deallocate(sbi, &ni->file.run, false);
 
 out4:
        clear_rec_inuse(rec);
index bfb7493c521649488f40b38d275d6c81e4cd83b2..8dd835626c7e9a02aa6dc6b28d863d16714138e4 100644 (file)
@@ -797,12 +797,12 @@ int run_pack(const struct runs_tree *run, CLST svcn, CLST len, u8 *run_buf,
             u32 run_buf_size, CLST *packed_vcns);
 int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
               CLST svcn, CLST evcn, CLST vcn, const u8 *run_buf,
-              u32 run_buf_size);
+              int run_buf_size);
 
 #ifdef NTFS3_CHECK_FREE_CLST
 int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
                  CLST svcn, CLST evcn, CLST vcn, const u8 *run_buf,
-                 u32 run_buf_size);
+                 int run_buf_size);
 #else
 #define run_unpack_ex run_unpack
 #endif
index aaaa0d3d35a24fbe9afb889aafedb3254c43c43a..12d8682f33b538dea9dcd1120b6a59a8a7999ccd 100644 (file)
@@ -919,12 +919,15 @@ out:
  */
 int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
               CLST svcn, CLST evcn, CLST vcn, const u8 *run_buf,
-              u32 run_buf_size)
+              int run_buf_size)
 {
        u64 prev_lcn, vcn64, lcn, next_vcn;
        const u8 *run_last, *run_0;
        bool is_mft = ino == MFT_REC_MFT;
 
+       if (run_buf_size < 0)
+               return -EINVAL;
+
        /* Check for empty. */
        if (evcn + 1 == svcn)
                return 0;
@@ -1046,7 +1049,7 @@ int run_unpack(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
  */
 int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
                  CLST svcn, CLST evcn, CLST vcn, const u8 *run_buf,
-                 u32 run_buf_size)
+                 int run_buf_size)
 {
        int ret, err;
        CLST next_vcn, lcn, len;
index 8620a7b4b3e63e51cdeaf74393f3b13b6cdb4544..dd3307117513496d384a8950c211ab8866f4770d 100644 (file)
@@ -43,28 +43,26 @@ static inline size_t packed_ea_size(const struct EA_FULL *ea)
  * Assume there is at least one xattr in the list.
  */
 static inline bool find_ea(const struct EA_FULL *ea_all, u32 bytes,
-                          const char *name, u8 name_len, u32 *off)
+                          const char *name, u8 name_len, u32 *off, u32 *ea_sz)
 {
-       *off = 0;
+       u32 ea_size;
 
-       if (!ea_all || !bytes)
+       *off = 0;
+       if (!ea_all)
                return false;
 
-       for (;;) {
+       for (; *off < bytes; *off += ea_size) {
                const struct EA_FULL *ea = Add2Ptr(ea_all, *off);
-               u32 next_off = *off + unpacked_ea_size(ea);
-
-               if (next_off > bytes)
-                       return false;
-
+               ea_size = unpacked_ea_size(ea);
                if (ea->name_len == name_len &&
-                   !memcmp(ea->name, name, name_len))
+                   !memcmp(ea->name, name, name_len)) {
+                       if (ea_sz)
+                               *ea_sz = ea_size;
                        return true;
-
-               *off = next_off;
-               if (next_off >= bytes)
-                       return false;
+               }
        }
+
+       return false;
 }
 
 /*
@@ -75,12 +73,12 @@ static inline bool find_ea(const struct EA_FULL *ea_all, u32 bytes,
 static int ntfs_read_ea(struct ntfs_inode *ni, struct EA_FULL **ea,
                        size_t add_bytes, const struct EA_INFO **info)
 {
-       int err;
+       int err = -EINVAL;
        struct ntfs_sb_info *sbi = ni->mi.sbi;
        struct ATTR_LIST_ENTRY *le = NULL;
        struct ATTRIB *attr_info, *attr_ea;
        void *ea_p;
-       u32 size;
+       u32 size, off, ea_size;
 
        static_assert(le32_to_cpu(ATTR_EA_INFO) < le32_to_cpu(ATTR_EA));
 
@@ -97,24 +95,31 @@ static int ntfs_read_ea(struct ntfs_inode *ni, struct EA_FULL **ea,
 
        *info = resident_data_ex(attr_info, sizeof(struct EA_INFO));
        if (!*info)
-               return -EINVAL;
+               goto out;
 
        /* Check Ea limit. */
        size = le32_to_cpu((*info)->size);
-       if (size > sbi->ea_max_size)
-               return -EFBIG;
+       if (size > sbi->ea_max_size) {
+               err = -EFBIG;
+               goto out;
+       }
+
+       if (attr_size(attr_ea) > sbi->ea_max_size) {
+               err = -EFBIG;
+               goto out;
+       }
 
-       if (attr_size(attr_ea) > sbi->ea_max_size)
-               return -EFBIG;
+       if (!size) {
+               /* EA info persists, but xattr is empty. Looks like EA problem. */
+               goto out;
+       }
 
        /* Allocate memory for packed Ea. */
        ea_p = kmalloc(size_add(size, add_bytes), GFP_NOFS);
        if (!ea_p)
                return -ENOMEM;
 
-       if (!size) {
-               /* EA info persists, but xattr is empty. Looks like EA problem. */
-       } else if (attr_ea->non_res) {
+       if (attr_ea->non_res) {
                struct runs_tree run;
 
                run_init(&run);
@@ -125,24 +130,52 @@ static int ntfs_read_ea(struct ntfs_inode *ni, struct EA_FULL **ea,
                run_close(&run);
 
                if (err)
-                       goto out;
+                       goto out1;
        } else {
                void *p = resident_data_ex(attr_ea, size);
 
-               if (!p) {
-                       err = -EINVAL;
-                       goto out;
-               }
+               if (!p)
+                       goto out1;
                memcpy(ea_p, p, size);
        }
 
        memset(Add2Ptr(ea_p, size), 0, add_bytes);
+
+       /* Check all attributes for consistency. */
+       for (off = 0; off < size; off += ea_size) {
+               const struct EA_FULL *ef = Add2Ptr(ea_p, off);
+               u32 bytes = size - off;
+
+               /* Check if we can use field ea->size. */
+               if (bytes < sizeof(ef->size))
+                       goto out1;
+
+               if (ef->size) {
+                       ea_size = le32_to_cpu(ef->size);
+                       if (ea_size > bytes)
+                               goto out1;
+                       continue;
+               }
+
+               /* Check if we can use fields ef->name_len and ef->elength. */
+               if (bytes < offsetof(struct EA_FULL, name))
+                       goto out1;
+
+               ea_size = ALIGN(struct_size(ef, name,
+                                           1 + ef->name_len +
+                                                   le16_to_cpu(ef->elength)),
+                               4);
+               if (ea_size > bytes)
+                       goto out1;
+       }
+
        *ea = ea_p;
        return 0;
 
-out:
+out1:
        kfree(ea_p);
-       *ea = NULL;
+out:
+       ntfs_set_state(sbi, NTFS_DIRTY_DIRTY);
        return err;
 }
 
@@ -164,6 +197,7 @@ static ssize_t ntfs_list_ea(struct ntfs_inode *ni, char *buffer,
        const struct EA_FULL *ea;
        u32 off, size;
        int err;
+       int ea_size;
        size_t ret;
 
        err = ntfs_read_ea(ni, &ea_all, 0, &info);
@@ -176,8 +210,9 @@ static ssize_t ntfs_list_ea(struct ntfs_inode *ni, char *buffer,
        size = le32_to_cpu(info->size);
 
        /* Enumerate all xattrs. */
-       for (ret = 0, off = 0; off < size; off += unpacked_ea_size(ea)) {
+       for (ret = 0, off = 0; off < size; off += ea_size) {
                ea = Add2Ptr(ea_all, off);
+               ea_size = unpacked_ea_size(ea);
 
                if (buffer) {
                        if (ret + ea->name_len + 1 > bytes_per_buffer) {
@@ -228,7 +263,8 @@ static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len,
                goto out;
 
        /* Enumerate all xattrs. */
-       if (!find_ea(ea_all, le32_to_cpu(info->size), name, name_len, &off)) {
+       if (!find_ea(ea_all, le32_to_cpu(info->size), name, name_len, &off,
+                    NULL)) {
                err = -ENODATA;
                goto out;
        }
@@ -270,7 +306,7 @@ static noinline int ntfs_set_ea(struct inode *inode, const char *name,
        struct EA_FULL *new_ea;
        struct EA_FULL *ea_all = NULL;
        size_t add, new_pack;
-       u32 off, size;
+       u32 off, size, ea_sz;
        __le16 size_pack;
        struct ATTRIB *attr;
        struct ATTR_LIST_ENTRY *le;
@@ -305,9 +341,8 @@ static noinline int ntfs_set_ea(struct inode *inode, const char *name,
                size_pack = ea_info.size_pack;
        }
 
-       if (info && find_ea(ea_all, size, name, name_len, &off)) {
+       if (info && find_ea(ea_all, size, name, name_len, &off, &ea_sz)) {
                struct EA_FULL *ea;
-               size_t ea_sz;
 
                if (flags & XATTR_CREATE) {
                        err = -EEXIST;
@@ -330,8 +365,6 @@ static noinline int ntfs_set_ea(struct inode *inode, const char *name,
                if (ea->flags & FILE_NEED_EA)
                        le16_add_cpu(&ea_info.count, -1);
 
-               ea_sz = unpacked_ea_size(ea);
-
                le16_add_cpu(&ea_info.size_pack, 0 - packed_ea_size(ea));
 
                memmove(ea, Add2Ptr(ea, ea_sz), size - off - ea_sz);