mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 22:19:30 +02:00
Merge tag 'hfs-v7.3-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs
Pull HFS updates from Viacheslav Dubeyko:
"This contains several fixes in HFS/HFS+ of syzbot reported issues and
HFS/HFS+ fixes of xfstests failures.
- b-tree bitmap corruption check (Aditya Prakash Srivastava)
During b-tree open (hfs_btree_open()), the code verifies that the
allocation map bit for the tree header (node 0) is set. If not, it
indicates a corrupted map record/bitmap and mounts the volume as
read-only (SB_RDONLY) to prevent further damage.
- Validate catalog CNIDs before instantiating inodes (David
Maximiliano Hermitte)
The hfs_cat_find_brec() first resolves a catalog thread record by
CNID and then looks up the corresponding catalog record by
parent/name. On a corrupted filesystem image, the second lookup may
find a record whose CNID does not match the CNID that was
requested. Finally, corrupted catalog records are rejected.
- Validate B-tree record offset table (Jiaming Zhang)
A crafted HFS+ image can contain a corrupted B-tree node. The node
descriptor may contain a record count that does not fit in the
node, and record offsets may be unordered, unaligned, outside the
node, or point into the offset table itself. Validate num_recs
against the node size before walking the record offset table.
Reject record ranges that are unordered, unaligned, outside the
node, or overlapping the offset table. Reject invalid record
indexes before reading their offset entries, and avoid decrementing
an already-zero leaf_count.
- Refactoring of hfsplus_delete_cat() logic (Kyle Zeng).
The hfsplus_delete_cat() is called with str == NULL when the last
open reference to an unlinked HFS+ hardlink backing inode is
closed. In that case, the function finds the catalog thread by CNID
and rebuilds the catalog key from thread.nodeName. A corrupted
image can therefore provide an oversized thread name length and
make hfs_bnode_read() write past the catalog search-key allocation.
Read the CNID record through hfsplus_brec_read_cat(), which bounds
the record read to sizeof(hfsplus_cat_entry) and verifies that a
thread record's size exactly matches nodeName.length.
- Cleanup in KUnit test (Mohammad Shahid)
The kfree() safely handles NULL pointers, so the explicit NULL
check in free_mock_str_env() before calling kfree() is unnecessary.
The rest contain fixes of generic/564 xfstests' test-case failure
for the case of HFS+ file system, syzbot reported issue in
hfs_mdb_commit() and hfs_mdb_close() methods of HFS file system,
and reworking the MDB locking scheme in HFS file system"
* tag 'hfs-v7.3-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs:
hfsplus: validate extent record length before writing it back
hfsplus: validate B-tree record offset table
hfs: rework MDB locking scheme
fs: hfsplus: remove redundant NULL check before kfree()
hfs: port HFS+ b-tree bitmap corruption check
hfs: don't re-dirty MDB buffers after a write failure
hfsplus: fix error code when writing beyond volume capacity
hfs: fix error code when writing beyond volume capacity
hfsplus: validate thread record before delete key rebuild
hfs: validate catalog CNIDs before instantiating inodes
This commit is contained in:
@@ -15,48 +15,6 @@
|
||||
|
||||
#include "btree.h"
|
||||
|
||||
static inline
|
||||
bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
|
||||
{
|
||||
bool is_valid = off < node->tree->node_size;
|
||||
|
||||
if (!is_valid) {
|
||||
pr_err("requested invalid offset: "
|
||||
"NODE: id %u, type %#x, height %u, "
|
||||
"node_size %u, offset %u\n",
|
||||
node->this, node->type, node->height,
|
||||
node->tree->node_size, off);
|
||||
}
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
static inline
|
||||
u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
|
||||
{
|
||||
unsigned int node_size;
|
||||
|
||||
if (!is_bnode_offset_valid(node, off))
|
||||
return 0;
|
||||
|
||||
node_size = node->tree->node_size;
|
||||
|
||||
if ((u64)off + len > node_size) {
|
||||
u32 new_len = node_size - off;
|
||||
|
||||
pr_err("requested length has been corrected: "
|
||||
"NODE: id %u, type %#x, height %u, "
|
||||
"node_size %u, offset %u, "
|
||||
"requested_len %u, corrected_len %u\n",
|
||||
node->this, node->type, node->height,
|
||||
node->tree->node_size, off, len, new_len);
|
||||
|
||||
return new_len;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void hfs_bnode_read(struct hfs_bnode *node, void *buf, u32 off, u32 len)
|
||||
{
|
||||
struct page *page;
|
||||
|
||||
+168
-42
@@ -15,6 +15,121 @@
|
||||
|
||||
#include "btree.h"
|
||||
|
||||
/* Context for iterating b-tree map pages
|
||||
* @page_idx: The index of the page within the b-node's page array
|
||||
* @off: The byte offset within the mapped page
|
||||
* @len: The remaining length of the map record
|
||||
*/
|
||||
struct hfs_bmap_ctx {
|
||||
unsigned int page_idx;
|
||||
unsigned int off;
|
||||
u16 len;
|
||||
};
|
||||
|
||||
/*
|
||||
* Finds the specific page containing the requested byte offset within the map
|
||||
* record. Automatically handles the difference between header and map nodes.
|
||||
* Returns the struct page pointer, or an ERR_PTR on failure.
|
||||
* Note: The caller is responsible for mapping/unmapping the returned page.
|
||||
*/
|
||||
static struct page *hfs_bmap_get_map_page(struct hfs_bnode *node,
|
||||
struct hfs_bmap_ctx *ctx,
|
||||
u32 byte_offset)
|
||||
{
|
||||
u16 rec_idx, off16;
|
||||
unsigned int page_off;
|
||||
|
||||
if (node->this == HFS_TREE_HEAD) {
|
||||
if (node->type != HFS_NODE_HEADER) {
|
||||
pr_err("hfs: invalid btree header node\n");
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
rec_idx = HFS_BTREE_HDR_MAP_REC_INDEX;
|
||||
} else {
|
||||
if (node->type != HFS_NODE_MAP) {
|
||||
pr_err("hfs: invalid btree map node\n");
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
rec_idx = HFS_BTREE_MAP_NODE_REC_INDEX;
|
||||
}
|
||||
|
||||
ctx->len = hfs_brec_lenoff(node, rec_idx, &off16);
|
||||
if (!ctx->len)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
if (!is_bnode_offset_valid(node, off16))
|
||||
return ERR_PTR(-EIO);
|
||||
|
||||
ctx->len = check_and_correct_requested_length(node, off16, ctx->len);
|
||||
|
||||
if (byte_offset >= ctx->len)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
page_off = (u32)off16 + node->page_offset + byte_offset;
|
||||
ctx->page_idx = page_off >> PAGE_SHIFT;
|
||||
ctx->off = page_off & ~PAGE_MASK;
|
||||
|
||||
return node->page[ctx->page_idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* hfs_bmap_test_bit - test a bit in the b-tree map
|
||||
* @node: the b-tree node containing the map record
|
||||
* @node_bit_idx: the relative bit index within the node's map record
|
||||
*
|
||||
* Returns true if set, false if clear or on failure.
|
||||
*/
|
||||
static bool hfs_bmap_test_bit(struct hfs_bnode *node, u32 node_bit_idx)
|
||||
{
|
||||
struct hfs_bmap_ctx ctx;
|
||||
struct page *page;
|
||||
u8 *bmap, byte, mask;
|
||||
|
||||
page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE);
|
||||
if (IS_ERR(page))
|
||||
return false;
|
||||
|
||||
bmap = kmap_local_page(page);
|
||||
byte = bmap[ctx.off];
|
||||
kunmap_local(bmap);
|
||||
|
||||
mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE));
|
||||
return (byte & mask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hfs_bmap_clear_bit - clear a bit in the b-tree map
|
||||
* @node: the b-tree node containing the map record
|
||||
* @node_bit_idx: the relative bit index within the node's map record
|
||||
*
|
||||
* Returns 0 on success, -EINVAL if already clear, or negative error code.
|
||||
*/
|
||||
static int hfs_bmap_clear_bit(struct hfs_bnode *node, u32 node_bit_idx)
|
||||
{
|
||||
struct hfs_bmap_ctx ctx;
|
||||
struct page *page;
|
||||
u8 *bmap, mask;
|
||||
|
||||
page = hfs_bmap_get_map_page(node, &ctx, node_bit_idx / BITS_PER_BYTE);
|
||||
if (IS_ERR(page))
|
||||
return PTR_ERR(page);
|
||||
|
||||
bmap = kmap_local_page(page);
|
||||
|
||||
mask = 1 << (7 - (node_bit_idx % BITS_PER_BYTE));
|
||||
|
||||
if (!(bmap[ctx.off] & mask)) {
|
||||
kunmap_local(bmap);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bmap[ctx.off] &= ~mask;
|
||||
set_page_dirty(page);
|
||||
kunmap_local(bmap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get a reference to a B*Tree and do some initial checks */
|
||||
struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp keycmp)
|
||||
{
|
||||
@@ -23,6 +138,7 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
|
||||
struct address_space *mapping;
|
||||
struct folio *folio;
|
||||
struct buffer_head *bh;
|
||||
struct hfs_bnode *node;
|
||||
unsigned int size;
|
||||
u16 dblock;
|
||||
sector_t start_block;
|
||||
@@ -155,6 +271,20 @@ struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, btree_keycmp ke
|
||||
kunmap_local(head);
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
|
||||
node = hfs_bnode_find(tree, HFS_TREE_HEAD);
|
||||
if (IS_ERR(node))
|
||||
goto free_inode;
|
||||
|
||||
if (!hfs_bmap_test_bit(node, HFS_TREE_HEAD)) {
|
||||
pr_warn("(%s): %s (cnid 0x%x) bitmap corrupted, forcing rdonly\n",
|
||||
sb->s_id, id == HFS_EXT_CNID ? "extents" : "catalog", id);
|
||||
pr_warn("Run fsck.hfs to repair.\n");
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
}
|
||||
|
||||
hfs_bnode_put(node);
|
||||
|
||||
return tree;
|
||||
|
||||
fail_folio:
|
||||
@@ -285,11 +415,9 @@ int hfs_bmap_reserve(struct hfs_btree *tree, u32 rsvd_nodes)
|
||||
struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
{
|
||||
struct hfs_bnode *node, *next_node;
|
||||
struct page **pagep;
|
||||
struct hfs_bmap_ctx ctx;
|
||||
struct page *page;
|
||||
u32 nidx, idx;
|
||||
unsigned off;
|
||||
u16 off16;
|
||||
u16 len;
|
||||
u8 *data, byte, m;
|
||||
int i, res;
|
||||
|
||||
@@ -301,24 +429,26 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
node = hfs_bnode_find(tree, nidx);
|
||||
if (IS_ERR(node))
|
||||
return node;
|
||||
len = hfs_brec_lenoff(node, 2, &off16);
|
||||
off = off16;
|
||||
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_SHIFT);
|
||||
data = kmap_local_page(*pagep);
|
||||
off &= ~PAGE_MASK;
|
||||
page = hfs_bmap_get_map_page(node, &ctx, 0);
|
||||
if (IS_ERR(page)) {
|
||||
res = PTR_ERR(page);
|
||||
hfs_bnode_put(node);
|
||||
return ERR_PTR(res);
|
||||
}
|
||||
|
||||
data = kmap_local_page(page);
|
||||
idx = 0;
|
||||
|
||||
for (;;) {
|
||||
while (len) {
|
||||
byte = data[off];
|
||||
while (ctx.len) {
|
||||
byte = data[ctx.off];
|
||||
if (byte != 0xff) {
|
||||
for (m = 0x80, i = 0; i < 8; m >>= 1, i++) {
|
||||
if (!(byte & m)) {
|
||||
idx += i;
|
||||
data[off] |= m;
|
||||
set_page_dirty(*pagep);
|
||||
data[ctx.off] |= m;
|
||||
set_page_dirty(page);
|
||||
kunmap_local(data);
|
||||
tree->free_nodes--;
|
||||
mark_inode_dirty(tree->inode);
|
||||
@@ -327,13 +457,14 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (++off >= PAGE_SIZE) {
|
||||
if (++ctx.off >= PAGE_SIZE) {
|
||||
kunmap_local(data);
|
||||
data = kmap_local_page(*++pagep);
|
||||
off = 0;
|
||||
page = node->page[++ctx.page_idx];
|
||||
data = kmap_local_page(page);
|
||||
ctx.off = 0;
|
||||
}
|
||||
idx += 8;
|
||||
len--;
|
||||
ctx.len--;
|
||||
}
|
||||
kunmap_local(data);
|
||||
nidx = node->next;
|
||||
@@ -347,22 +478,22 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
|
||||
return next_node;
|
||||
node = next_node;
|
||||
|
||||
len = hfs_brec_lenoff(node, 0, &off16);
|
||||
off = off16;
|
||||
off += node->page_offset;
|
||||
pagep = node->page + (off >> PAGE_SHIFT);
|
||||
data = kmap_local_page(*pagep);
|
||||
off &= ~PAGE_MASK;
|
||||
page = hfs_bmap_get_map_page(node, &ctx, 0);
|
||||
if (IS_ERR(page)) {
|
||||
res = PTR_ERR(page);
|
||||
hfs_bnode_put(node);
|
||||
return ERR_PTR(res);
|
||||
}
|
||||
data = kmap_local_page(page);
|
||||
}
|
||||
}
|
||||
|
||||
void hfs_bmap_free(struct hfs_bnode *node)
|
||||
{
|
||||
struct hfs_btree *tree;
|
||||
struct page *page;
|
||||
u16 off, len;
|
||||
u32 nidx;
|
||||
u8 *data, byte, m;
|
||||
int res;
|
||||
|
||||
hfs_dbg("node %u\n", node->this);
|
||||
tree = node->tree;
|
||||
@@ -396,23 +527,18 @@ void hfs_bmap_free(struct hfs_bnode *node)
|
||||
}
|
||||
len = hfs_brec_lenoff(node, 0, &off);
|
||||
}
|
||||
off += node->page_offset + nidx / 8;
|
||||
page = node->page[off >> PAGE_SHIFT];
|
||||
data = kmap_local_page(page);
|
||||
off &= ~PAGE_MASK;
|
||||
m = 1 << (~nidx & 7);
|
||||
byte = data[off];
|
||||
if (!(byte & m)) {
|
||||
|
||||
res = hfs_bmap_clear_bit(node, nidx);
|
||||
if (res == -EINVAL) {
|
||||
pr_crit("trying to free free bnode %u(%d)\n",
|
||||
node->this, node->type);
|
||||
kunmap_local(data);
|
||||
hfs_bnode_put(node);
|
||||
return;
|
||||
nidx, node->type);
|
||||
} else if (res) {
|
||||
pr_crit("fail to free bnode %u(%d)\n",
|
||||
nidx, node->type);
|
||||
} else {
|
||||
tree->free_nodes++;
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
||||
data[off] = byte & ~m;
|
||||
set_page_dirty(page);
|
||||
kunmap_local(data);
|
||||
|
||||
hfs_bnode_put(node);
|
||||
tree->free_nodes++;
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
||||
|
||||
@@ -129,3 +129,43 @@ extern int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd);
|
||||
extern int hfs_brec_find(struct hfs_find_data *fd);
|
||||
extern int hfs_brec_read(struct hfs_find_data *fd, void *rec, u32 rec_len);
|
||||
extern int hfs_brec_goto(struct hfs_find_data *fd, int cnt);
|
||||
|
||||
static inline bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
|
||||
{
|
||||
bool is_valid;
|
||||
|
||||
if (!node || !node->tree)
|
||||
return false;
|
||||
|
||||
is_valid = off < node->tree->node_size;
|
||||
|
||||
if (!is_valid) {
|
||||
pr_err("invalid offset: id %u, type %#x, h %u, sz %u, off %u\n",
|
||||
node->this, node->type, node->height,
|
||||
node->tree->node_size, off);
|
||||
}
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
static inline u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
|
||||
{
|
||||
unsigned int node_size;
|
||||
|
||||
if (!is_bnode_offset_valid(node, off))
|
||||
return 0;
|
||||
|
||||
node_size = node->tree->node_size;
|
||||
|
||||
if ((u64)off + len > node_size) {
|
||||
u32 new_len = node_size - off;
|
||||
|
||||
pr_err("corrected len: id %u, type %#x, h %u, sz %u, off %u, len %u->%u\n",
|
||||
node->this, node->type, node->height,
|
||||
node_size, off, len, new_len);
|
||||
|
||||
return new_len;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
+40
-1
@@ -182,6 +182,40 @@ int hfs_cat_keycmp(const btree_key *key1, const btree_key *key2)
|
||||
key2->cat.CName.name, key2->cat.CName.len);
|
||||
}
|
||||
|
||||
static int hfs_cat_validate_found_cnid(struct hfs_find_data *fd, u32 cnid)
|
||||
{
|
||||
hfs_cat_rec rec;
|
||||
u32 found_cnid;
|
||||
int rec_len;
|
||||
|
||||
rec_len = fd->entrylength;
|
||||
if (rec_len <= 0 || (size_t)rec_len > sizeof(rec))
|
||||
return -EIO;
|
||||
|
||||
memset(&rec, 0, sizeof(rec));
|
||||
hfs_bnode_read(fd->bnode, &rec, fd->entryoffset, rec_len);
|
||||
|
||||
switch (rec.type) {
|
||||
case HFS_CDR_FIL:
|
||||
if ((size_t)rec_len != sizeof(struct hfs_cat_file))
|
||||
return -EIO;
|
||||
found_cnid = be32_to_cpu(rec.file.FlNum);
|
||||
break;
|
||||
case HFS_CDR_DIR:
|
||||
if ((size_t)rec_len != sizeof(struct hfs_cat_dir))
|
||||
return -EIO;
|
||||
found_cnid = be32_to_cpu(rec.dir.DirID);
|
||||
break;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (found_cnid != cnid)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Try to get a catalog entry for given catalog id */
|
||||
// move to read_super???
|
||||
int hfs_cat_find_brec(struct super_block *sb, u32 cnid,
|
||||
@@ -208,7 +242,12 @@ int hfs_cat_find_brec(struct super_block *sb, u32 cnid,
|
||||
return -EIO;
|
||||
}
|
||||
memcpy(fd->search_key->cat.CName.name, rec.thread.CName.name, len);
|
||||
return hfs_brec_find(fd);
|
||||
|
||||
res = hfs_brec_find(fd);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
return hfs_cat_validate_found_cnid(fd, cnid);
|
||||
}
|
||||
|
||||
static inline
|
||||
|
||||
+30
-3
@@ -64,14 +64,22 @@ struct hfs_inode_info {
|
||||
* The HFS-specific part of a Linux (struct super_block)
|
||||
*/
|
||||
struct hfs_sb_info {
|
||||
struct mutex mdb_lock; /* MDB operations lock */
|
||||
struct buffer_head *mdb_bh; /* The hfs_buffer
|
||||
holding the real
|
||||
superblock (aka VIB
|
||||
or MDB) */
|
||||
struct hfs_mdb *mdb;
|
||||
unsigned int mdb_offset; /* byte offset of the MDB
|
||||
sector within mdb_bh's
|
||||
data */
|
||||
struct hfs_mdb *mdb; /* in-memory copy of the MDB */
|
||||
struct buffer_head *alt_mdb_bh; /* The hfs_buffer holding
|
||||
the alternate superblock */
|
||||
struct hfs_mdb *alt_mdb;
|
||||
unsigned int alt_mdb_offset; /* byte offset of the alternate
|
||||
MDB sector within
|
||||
alt_mdb_bh's data */
|
||||
struct hfs_mdb *alt_mdb; /* in-memory copy of the
|
||||
alternate MDB */
|
||||
__be32 *bitmap; /* The page holding the
|
||||
allocation bitmap */
|
||||
struct hfs_btree *ext_tree; /* Information about
|
||||
@@ -155,6 +163,25 @@ extern int hfs_cat_move(u32 cnid, struct inode *src_dir,
|
||||
extern void hfs_cat_build_key(struct super_block *sb, btree_key *key,
|
||||
u32 parent, const struct qstr *name);
|
||||
|
||||
/*
|
||||
* Validate the CNID of a catalog record.
|
||||
*/
|
||||
static inline bool hfs_is_valid_cnid(u32 cnid, u8 type)
|
||||
{
|
||||
if (likely(cnid >= HFS_FIRSTUSER_CNID))
|
||||
return true;
|
||||
|
||||
switch (cnid) {
|
||||
case HFS_ROOT_CNID:
|
||||
return type == HFS_CDR_DIR;
|
||||
case HFS_EXT_CNID:
|
||||
case HFS_CAT_CNID:
|
||||
return type == HFS_CDR_FIL;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* dir.c */
|
||||
extern const struct file_operations hfs_dir_operations;
|
||||
extern const struct inode_operations hfs_dir_inode_operations;
|
||||
@@ -201,7 +228,7 @@ extern const struct xattr_handler * const hfs_xattr_handlers[];
|
||||
/* mdb.c */
|
||||
extern bool is_hfs_cnid_counts_valid(struct super_block *sb);
|
||||
extern int hfs_mdb_get(struct super_block *sb);
|
||||
extern void hfs_mdb_commit(struct super_block *sb);
|
||||
extern int hfs_mdb_commit(struct super_block *sb);
|
||||
extern void hfs_mdb_close(struct super_block *sb);
|
||||
extern void hfs_mdb_put(struct super_block *sb);
|
||||
|
||||
|
||||
+21
-2
@@ -49,11 +49,19 @@ int hfs_write_begin(const struct kiocb *iocb, struct address_space *mapping,
|
||||
loff_t pos, unsigned int len, struct folio **foliop,
|
||||
void **fsdata)
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct hfs_sb_info *sbi = HFS_SB(inode->i_sb);
|
||||
loff_t total_capacity;
|
||||
int ret;
|
||||
|
||||
total_capacity = (loff_t)sbi->fs_ablocks * sbi->alloc_blksz;
|
||||
|
||||
if (pos >= total_capacity)
|
||||
return -EFBIG;
|
||||
|
||||
ret = cont_write_begin(iocb, mapping, pos, len, foliop, fsdata,
|
||||
hfs_get_block,
|
||||
&HFS_I(mapping->host)->phys_size);
|
||||
&HFS_I(inode)->phys_size);
|
||||
if (unlikely(ret))
|
||||
hfs_write_failed(mapping, pos + len);
|
||||
|
||||
@@ -367,6 +375,9 @@ static int hfs_read_inode(struct inode *inode, void *data)
|
||||
rec = idata->rec;
|
||||
switch (rec->type) {
|
||||
case HFS_CDR_FIL:
|
||||
if (!hfs_is_valid_cnid(be32_to_cpu(rec->file.FlNum), rec->type))
|
||||
return -EIO;
|
||||
|
||||
if (!HFS_IS_RSRC(inode)) {
|
||||
hfs_inode_read_fork(inode, rec->file.ExtRec, rec->file.LgLen,
|
||||
rec->file.PyLen, be16_to_cpu(rec->file.ClpSize));
|
||||
@@ -390,6 +401,9 @@ static int hfs_read_inode(struct inode *inode, void *data)
|
||||
inode->i_mapping->a_ops = &hfs_aops;
|
||||
break;
|
||||
case HFS_CDR_DIR:
|
||||
if (!hfs_is_valid_cnid(be32_to_cpu(rec->dir.DirID), rec->type))
|
||||
return -EIO;
|
||||
|
||||
inode->i_ino = be32_to_cpu(rec->dir.DirID);
|
||||
inode->i_size = be16_to_cpu(rec->dir.Val) + 2;
|
||||
HFS_I(inode)->fs_blocks = 0;
|
||||
@@ -571,13 +585,18 @@ static struct dentry *hfs_file_lookup(struct inode *dir, struct dentry *dentry,
|
||||
res = hfs_brec_read(&fd, &rec, sizeof(rec));
|
||||
if (!res) {
|
||||
struct hfs_iget_data idata = { NULL, &rec };
|
||||
hfs_read_inode(inode, &idata);
|
||||
res = hfs_read_inode(inode, &idata);
|
||||
}
|
||||
hfs_find_exit(&fd);
|
||||
if (res) {
|
||||
iput(inode);
|
||||
return ERR_PTR(res);
|
||||
}
|
||||
|
||||
if (is_bad_inode(inode)) {
|
||||
iput(inode);
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
HFS_I(inode)->rsrc_inode = dir;
|
||||
HFS_I(dir)->rsrc_inode = inode;
|
||||
igrab(dir);
|
||||
|
||||
+89
-21
@@ -85,6 +85,39 @@ bool is_hfs_cnid_counts_valid(struct super_block *sb)
|
||||
return !corrupted;
|
||||
}
|
||||
|
||||
/*
|
||||
* hfs_sect_offset() - get byte offset within the buffer_head.
|
||||
*/
|
||||
static unsigned int hfs_sect_offset(struct super_block *sb, sector_t sec)
|
||||
{
|
||||
loff_t start = (loff_t)sec << HFS_SECTOR_SIZE_BITS;
|
||||
|
||||
return start & (sb->s_blocksize - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* hfs_mdb_publish() - copy the in-memory primary MDB to the on-disk buffer.
|
||||
*/
|
||||
static void hfs_mdb_publish(struct hfs_sb_info *sbi)
|
||||
{
|
||||
lock_buffer(sbi->mdb_bh);
|
||||
memcpy(sbi->mdb_bh->b_data + sbi->mdb_offset, sbi->mdb, HFS_SECTOR_SIZE);
|
||||
mark_buffer_dirty(sbi->mdb_bh);
|
||||
unlock_buffer(sbi->mdb_bh);
|
||||
}
|
||||
|
||||
/*
|
||||
* hfs_alt_mdb_publish() - copy the in-memory alternate MDB to its buffer.
|
||||
*/
|
||||
static void hfs_alt_mdb_publish(struct hfs_sb_info *sbi)
|
||||
{
|
||||
lock_buffer(sbi->alt_mdb_bh);
|
||||
memcpy(sbi->alt_mdb_bh->b_data + sbi->alt_mdb_offset, sbi->alt_mdb,
|
||||
HFS_SECTOR_SIZE);
|
||||
mark_buffer_dirty(sbi->alt_mdb_bh);
|
||||
unlock_buffer(sbi->alt_mdb_bh);
|
||||
}
|
||||
|
||||
/*
|
||||
* hfs_mdb_get()
|
||||
*
|
||||
@@ -94,7 +127,7 @@ bool is_hfs_cnid_counts_valid(struct super_block *sb)
|
||||
int hfs_mdb_get(struct super_block *sb)
|
||||
{
|
||||
struct buffer_head *bh;
|
||||
struct hfs_mdb *mdb, *mdb2;
|
||||
struct hfs_mdb *mdb, *alt_mdb;
|
||||
unsigned int block;
|
||||
char *ptr;
|
||||
int off2, len, size, sect;
|
||||
@@ -158,7 +191,14 @@ int hfs_mdb_get(struct super_block *sb)
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mdb = kmemdup(mdb, HFS_SECTOR_SIZE, GFP_KERNEL);
|
||||
if (!mdb) {
|
||||
brelse(bh);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
HFS_SB(sb)->mdb_bh = bh;
|
||||
HFS_SB(sb)->mdb_offset = hfs_sect_offset(sb, part_start + HFS_MDB_BLK);
|
||||
HFS_SB(sb)->mdb = mdb;
|
||||
|
||||
/* These parameters are read from the MDB, and never written */
|
||||
@@ -187,11 +227,18 @@ int hfs_mdb_get(struct super_block *sb)
|
||||
|
||||
/* TRY to get the alternate (backup) MDB. */
|
||||
sect = part_start + part_size - 2;
|
||||
bh = sb_bread512(sb, sect, mdb2);
|
||||
bh = sb_bread512(sb, sect, alt_mdb);
|
||||
if (bh) {
|
||||
if (mdb2->drSigWord == cpu_to_be16(HFS_SUPER_MAGIC)) {
|
||||
HFS_SB(sb)->alt_mdb_bh = bh;
|
||||
HFS_SB(sb)->alt_mdb = mdb2;
|
||||
if (alt_mdb->drSigWord == cpu_to_be16(HFS_SUPER_MAGIC)) {
|
||||
alt_mdb = kmemdup(alt_mdb, HFS_SECTOR_SIZE, GFP_KERNEL);
|
||||
if (alt_mdb) {
|
||||
HFS_SB(sb)->alt_mdb_bh = bh;
|
||||
HFS_SB(sb)->alt_mdb_offset =
|
||||
hfs_sect_offset(sb, sect);
|
||||
HFS_SB(sb)->alt_mdb = alt_mdb;
|
||||
} else {
|
||||
brelse(bh);
|
||||
}
|
||||
} else
|
||||
brelse(bh);
|
||||
}
|
||||
@@ -253,7 +300,7 @@ int hfs_mdb_get(struct super_block *sb)
|
||||
be32_add_cpu(&mdb->drWrCnt, 1);
|
||||
mdb->drLsMod = hfs_mtime();
|
||||
|
||||
mark_buffer_dirty(HFS_SB(sb)->mdb_bh);
|
||||
hfs_mdb_publish(HFS_SB(sb));
|
||||
sync_dirty_buffer(HFS_SB(sb)->mdb_bh);
|
||||
}
|
||||
|
||||
@@ -274,7 +321,9 @@ int hfs_mdb_get(struct super_block *sb)
|
||||
* Output Variable(s):
|
||||
* NONE
|
||||
* Returns:
|
||||
* void
|
||||
* 0 on success, -EIO if the MDB or alternate MDB buffer is no longer
|
||||
* valid (e.g. after a prior write error), in which case the volume is
|
||||
* remounted read-only.
|
||||
* Preconditions:
|
||||
* 'mdb' points to a "valid" (struct hfs_mdb).
|
||||
* Postconditions:
|
||||
@@ -284,14 +333,20 @@ int hfs_mdb_get(struct super_block *sb)
|
||||
* If 'backup' is non-zero then the alternate MDB is also written
|
||||
* and the function doesn't return until it is actually on disk.
|
||||
*/
|
||||
void hfs_mdb_commit(struct super_block *sb)
|
||||
int hfs_mdb_commit(struct super_block *sb)
|
||||
{
|
||||
struct hfs_mdb *mdb = HFS_SB(sb)->mdb;
|
||||
int ret = 0;
|
||||
|
||||
if (sb_rdonly(sb))
|
||||
return;
|
||||
return 0;
|
||||
|
||||
if (!buffer_uptodate(HFS_SB(sb)->mdb_bh)) {
|
||||
pr_err("primary MDB is corrupt, mounting read-only\n");
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
lock_buffer(HFS_SB(sb)->mdb_bh);
|
||||
if (test_and_clear_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags)) {
|
||||
/* These parameters may have been modified, so write them back */
|
||||
mdb->drLsMod = hfs_mtime();
|
||||
@@ -305,8 +360,14 @@ void hfs_mdb_commit(struct super_block *sb)
|
||||
mdb->drDirCnt =
|
||||
cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->folder_count));
|
||||
|
||||
hfs_inode_write_fork(HFS_SB(sb)->ext_tree->inode, mdb->drXTExtRec,
|
||||
&mdb->drXTFlSize, NULL);
|
||||
hfs_inode_write_fork(HFS_SB(sb)->cat_tree->inode, mdb->drCTExtRec,
|
||||
&mdb->drCTFlSize, NULL);
|
||||
|
||||
/* write MDB to disk */
|
||||
mark_buffer_dirty(HFS_SB(sb)->mdb_bh);
|
||||
hfs_mdb_publish(HFS_SB(sb));
|
||||
sync_dirty_buffer(HFS_SB(sb)->mdb_bh);
|
||||
}
|
||||
|
||||
/* write the backup MDB, not returning until it is written.
|
||||
@@ -314,18 +375,18 @@ void hfs_mdb_commit(struct super_block *sb)
|
||||
* files grow. */
|
||||
if (test_and_clear_bit(HFS_FLG_ALT_MDB_DIRTY, &HFS_SB(sb)->flags) &&
|
||||
HFS_SB(sb)->alt_mdb) {
|
||||
hfs_inode_write_fork(HFS_SB(sb)->ext_tree->inode, mdb->drXTExtRec,
|
||||
&mdb->drXTFlSize, NULL);
|
||||
hfs_inode_write_fork(HFS_SB(sb)->cat_tree->inode, mdb->drCTExtRec,
|
||||
&mdb->drCTFlSize, NULL);
|
||||
if (!buffer_uptodate(HFS_SB(sb)->alt_mdb_bh)) {
|
||||
pr_err("alternate MDB is corrupt, mounting read-only\n");
|
||||
sb->s_flags |= SB_RDONLY;
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
lock_buffer(HFS_SB(sb)->alt_mdb_bh);
|
||||
memcpy(HFS_SB(sb)->alt_mdb, HFS_SB(sb)->mdb, HFS_SECTOR_SIZE);
|
||||
memcpy(HFS_SB(sb)->alt_mdb, mdb, HFS_SECTOR_SIZE);
|
||||
HFS_SB(sb)->alt_mdb->drAtrb |= cpu_to_be16(HFS_SB_ATTRIB_UNMNT);
|
||||
HFS_SB(sb)->alt_mdb->drAtrb &= cpu_to_be16(~HFS_SB_ATTRIB_INCNSTNT);
|
||||
unlock_buffer(HFS_SB(sb)->alt_mdb_bh);
|
||||
|
||||
mark_buffer_dirty(HFS_SB(sb)->alt_mdb_bh);
|
||||
hfs_alt_mdb_publish(HFS_SB(sb));
|
||||
sync_dirty_buffer(HFS_SB(sb)->alt_mdb_bh);
|
||||
}
|
||||
|
||||
@@ -360,7 +421,8 @@ void hfs_mdb_commit(struct super_block *sb)
|
||||
size -= len;
|
||||
}
|
||||
}
|
||||
unlock_buffer(HFS_SB(sb)->mdb_bh);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void hfs_mdb_close(struct super_block *sb)
|
||||
@@ -368,9 +430,13 @@ void hfs_mdb_close(struct super_block *sb)
|
||||
/* update volume attributes */
|
||||
if (sb_rdonly(sb))
|
||||
return;
|
||||
|
||||
if (!buffer_uptodate(HFS_SB(sb)->mdb_bh))
|
||||
return;
|
||||
|
||||
HFS_SB(sb)->mdb->drAtrb |= cpu_to_be16(HFS_SB_ATTRIB_UNMNT);
|
||||
HFS_SB(sb)->mdb->drAtrb &= cpu_to_be16(~HFS_SB_ATTRIB_INCNSTNT);
|
||||
mark_buffer_dirty(HFS_SB(sb)->mdb_bh);
|
||||
hfs_mdb_publish(HFS_SB(sb));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -386,6 +452,8 @@ void hfs_mdb_put(struct super_block *sb)
|
||||
/* free the buffers holding the primary and alternate MDBs */
|
||||
brelse(HFS_SB(sb)->mdb_bh);
|
||||
brelse(HFS_SB(sb)->alt_mdb_bh);
|
||||
kfree(HFS_SB(sb)->mdb);
|
||||
kfree(HFS_SB(sb)->alt_mdb);
|
||||
|
||||
unload_nls(HFS_SB(sb)->nls_io);
|
||||
unload_nls(HFS_SB(sb)->nls_disk);
|
||||
|
||||
+17
-3
@@ -34,9 +34,14 @@ MODULE_LICENSE("GPL");
|
||||
|
||||
static int hfs_sync_fs(struct super_block *sb, int wait)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&HFS_SB(sb)->mdb_lock);
|
||||
is_hfs_cnid_counts_valid(sb);
|
||||
hfs_mdb_commit(sb);
|
||||
return 0;
|
||||
ret = hfs_mdb_commit(sb);
|
||||
mutex_unlock(&HFS_SB(sb)->mdb_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -66,9 +71,10 @@ static void flush_mdb(struct work_struct *work)
|
||||
sbi->work_queued = 0;
|
||||
spin_unlock(&sbi->work_lock);
|
||||
|
||||
mutex_lock(&sbi->mdb_lock);
|
||||
is_hfs_cnid_counts_valid(sb);
|
||||
|
||||
hfs_mdb_commit(sb);
|
||||
mutex_unlock(&sbi->mdb_lock);
|
||||
}
|
||||
|
||||
void hfs_mark_mdb_dirty(struct super_block *sb)
|
||||
@@ -339,9 +345,12 @@ static int hfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
sb->s_op = &hfs_super_operations;
|
||||
sb->s_xattr = hfs_xattr_handlers;
|
||||
sb->s_flags |= SB_NOATIME | SB_NODIRATIME;
|
||||
mutex_init(&sbi->mdb_lock);
|
||||
mutex_init(&sbi->bitmap_lock);
|
||||
|
||||
mutex_lock(&sbi->mdb_lock);
|
||||
res = hfs_mdb_get(sb);
|
||||
mutex_unlock(&sbi->mdb_lock);
|
||||
if (res) {
|
||||
if (!silent)
|
||||
pr_warn("can't find a HFS filesystem on dev %s\n",
|
||||
@@ -372,6 +381,11 @@ static int hfs_fill_super(struct super_block *sb, struct fs_context *fc)
|
||||
if (!root_inode)
|
||||
goto bail_no_root;
|
||||
|
||||
if (is_bad_inode(root_inode)) {
|
||||
iput(root_inode);
|
||||
goto bail_no_root;
|
||||
}
|
||||
|
||||
set_default_d_op(sb, &hfs_dentry_operations);
|
||||
res = -ENOMEM;
|
||||
sb->s_root = d_make_root(root_inode);
|
||||
|
||||
+13
-10
@@ -18,6 +18,7 @@ int hfs_find_init(struct hfs_btree *tree, struct hfs_find_data *fd)
|
||||
|
||||
fd->tree = tree;
|
||||
fd->bnode = NULL;
|
||||
hfs_find_result_init(fd);
|
||||
ptr = kzalloc(tree->max_key_len * 2 + 4, GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return -ENOMEM;
|
||||
@@ -106,17 +107,21 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd,
|
||||
u16 off, len, keylen;
|
||||
int rec;
|
||||
int b, e;
|
||||
int res;
|
||||
int res = -ENOENT;
|
||||
|
||||
BUG_ON(!rec_found);
|
||||
hfs_find_result_init(fd);
|
||||
if (hfs_bnode_num_recs_invalid(bnode))
|
||||
goto fail;
|
||||
|
||||
b = 0;
|
||||
e = bnode->num_recs - 1;
|
||||
res = -ENOENT;
|
||||
do {
|
||||
rec = (e + b) / 2;
|
||||
len = hfs_brec_lenoff(bnode, rec, &off);
|
||||
keylen = hfs_brec_keylen(bnode, rec);
|
||||
if (keylen == 0) {
|
||||
if (hfs_brec_len_invalid(bnode, len) ||
|
||||
hfs_brec_len_invalid(bnode, keylen)) {
|
||||
res = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
@@ -130,7 +135,8 @@ int __hfs_brec_find(struct hfs_bnode *bnode, struct hfs_find_data *fd,
|
||||
if (rec != e && e >= 0) {
|
||||
len = hfs_brec_lenoff(bnode, e, &off);
|
||||
keylen = hfs_brec_keylen(bnode, e);
|
||||
if (keylen == 0) {
|
||||
if (hfs_brec_len_invalid(bnode, keylen) ||
|
||||
hfs_brec_len_invalid(bnode, len)) {
|
||||
res = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
@@ -158,11 +164,7 @@ int hfs_brec_find(struct hfs_find_data *fd, search_strategy_t do_key_compare)
|
||||
__be32 data;
|
||||
int height, res;
|
||||
|
||||
fd->record = -1;
|
||||
fd->keyoffset = -1;
|
||||
fd->keylength = -1;
|
||||
fd->entryoffset = -1;
|
||||
fd->entrylength = -1;
|
||||
hfs_find_result_init(fd);
|
||||
|
||||
tree = fd->tree;
|
||||
if (fd->bnode)
|
||||
@@ -274,7 +276,8 @@ int hfs_brec_goto(struct hfs_find_data *fd, int cnt)
|
||||
|
||||
len = hfs_brec_lenoff(bnode, fd->record, &off);
|
||||
keylen = hfs_brec_keylen(bnode, fd->record);
|
||||
if (keylen == 0) {
|
||||
if (hfs_brec_len_invalid(bnode, len) ||
|
||||
hfs_brec_len_invalid(bnode, keylen)) {
|
||||
res = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
+12
-4
@@ -352,15 +352,22 @@ void hfs_bnode_dump(struct hfs_bnode *node)
|
||||
struct hfs_bnode_desc desc;
|
||||
__be32 cnid;
|
||||
int i, off, key_off;
|
||||
u16 num_recs;
|
||||
|
||||
hfs_dbg("node %d\n", node->this);
|
||||
hfs_bnode_read(node, &desc, 0, sizeof(desc));
|
||||
num_recs = node->num_recs;
|
||||
hfs_dbg("next %d, prev %d, type %d, height %d, num_recs %d\n",
|
||||
be32_to_cpu(desc.next), be32_to_cpu(desc.prev),
|
||||
desc.type, desc.height, be16_to_cpu(desc.num_recs));
|
||||
|
||||
if (hfs_bnode_num_recs_invalid(node)) {
|
||||
hfs_dbg("invalid num_recs %u\n", num_recs);
|
||||
return;
|
||||
}
|
||||
|
||||
off = node->tree->node_size - 2;
|
||||
for (i = be16_to_cpu(desc.num_recs); i >= 0; off -= 2, i--) {
|
||||
for (i = num_recs; i >= 0; off -= 2, i--) {
|
||||
key_off = hfs_bnode_read_u16(node, off);
|
||||
hfs_dbg(" key_off %d", key_off);
|
||||
if (i && node->type == HFS_NODE_INDEX) {
|
||||
@@ -561,6 +568,9 @@ struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num)
|
||||
node->height = desc->height;
|
||||
kunmap_local(desc);
|
||||
|
||||
if (hfs_bnode_num_recs_invalid(node))
|
||||
goto node_error;
|
||||
|
||||
switch (node->type) {
|
||||
case HFS_NODE_HEADER:
|
||||
case HFS_NODE_MAP:
|
||||
@@ -586,9 +596,7 @@ struct hfs_bnode *hfs_bnode_find(struct hfs_btree *tree, u32 num)
|
||||
for (i = 1; i <= node->num_recs; off = next_off, i++) {
|
||||
rec_off -= 2;
|
||||
next_off = hfs_bnode_read_u16(node, rec_off);
|
||||
if (next_off <= off ||
|
||||
next_off > tree->node_size ||
|
||||
next_off & 1)
|
||||
if (hfs_brec_offsets_invalid(node, off, next_off))
|
||||
goto node_error;
|
||||
entry_size = next_off - off;
|
||||
if (node->type != HFS_NODE_INDEX &&
|
||||
|
||||
+37
-16
@@ -9,6 +9,8 @@
|
||||
* Handle individual btree records
|
||||
*/
|
||||
|
||||
#include <linux/limits.h>
|
||||
|
||||
#include "hfsplus_fs.h"
|
||||
#include "hfsplus_raw.h"
|
||||
|
||||
@@ -20,41 +22,49 @@ static int hfs_btree_inc_height(struct hfs_btree *);
|
||||
u16 hfs_brec_lenoff(struct hfs_bnode *node, u16 rec, u16 *off)
|
||||
{
|
||||
__be16 retval[2];
|
||||
u16 dataoff;
|
||||
u16 data_off;
|
||||
u16 next_off;
|
||||
|
||||
dataoff = node->tree->node_size - (rec + 2) * 2;
|
||||
hfs_bnode_read(node, retval, dataoff, 4);
|
||||
if (hfs_brec_record_invalid(node, rec)) {
|
||||
*off = U16_MAX;
|
||||
return U16_MAX;
|
||||
}
|
||||
|
||||
data_off = node->tree->node_size - (rec + 2) * 2;
|
||||
hfs_bnode_read(node, retval, data_off, 4);
|
||||
*off = be16_to_cpu(retval[1]);
|
||||
return be16_to_cpu(retval[0]) - *off;
|
||||
next_off = be16_to_cpu(retval[0]);
|
||||
if (hfs_brec_offsets_invalid(node, *off, next_off)) {
|
||||
*off = U16_MAX;
|
||||
return U16_MAX;
|
||||
}
|
||||
return next_off - *off;
|
||||
}
|
||||
|
||||
/* Get the length of the key from a keyed record */
|
||||
u16 hfs_brec_keylen(struct hfs_bnode *node, u16 rec)
|
||||
{
|
||||
u16 retval, recoff;
|
||||
u16 retval, recoff, len;
|
||||
|
||||
if (node->type != HFS_NODE_INDEX && node->type != HFS_NODE_LEAF)
|
||||
return 0;
|
||||
if (hfs_brec_record_invalid(node, rec))
|
||||
return U16_MAX;
|
||||
|
||||
if ((node->type == HFS_NODE_INDEX) &&
|
||||
!(node->tree->attributes & HFS_TREE_VARIDXKEYS) &&
|
||||
(node->tree->cnid != HFSPLUS_ATTR_CNID)) {
|
||||
retval = node->tree->max_key_len + 2;
|
||||
} else {
|
||||
recoff = hfs_bnode_read_u16(node,
|
||||
node->tree->node_size - (rec + 1) * 2);
|
||||
if (!recoff)
|
||||
return 0;
|
||||
if (recoff > node->tree->node_size - 2) {
|
||||
pr_err("recoff %d too large\n", recoff);
|
||||
return 0;
|
||||
}
|
||||
len = hfs_brec_lenoff(node, rec, &recoff);
|
||||
if (hfs_brec_len_invalid(node, len))
|
||||
return len;
|
||||
|
||||
retval = hfs_bnode_read_u16(node, recoff) + 2;
|
||||
if (retval > node->tree->max_key_len + 2) {
|
||||
pr_err("keylen %d too large\n",
|
||||
retval);
|
||||
retval = 0;
|
||||
retval = U16_MAX;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
@@ -181,14 +191,20 @@ int hfs_brec_remove(struct hfs_find_data *fd)
|
||||
struct hfs_btree *tree;
|
||||
struct hfs_bnode *node, *parent;
|
||||
int end_off, rec_off, data_off, size;
|
||||
int res;
|
||||
|
||||
tree = fd->tree;
|
||||
node = fd->bnode;
|
||||
again:
|
||||
if (hfs_brec_record_invalid(node, fd->record))
|
||||
return -EINVAL;
|
||||
|
||||
rec_off = tree->node_size - (fd->record + 2) * 2;
|
||||
end_off = tree->node_size - (node->num_recs + 1) * 2;
|
||||
|
||||
if (node->type == HFS_NODE_LEAF) {
|
||||
if (tree->leaf_count == 0)
|
||||
return -EINVAL;
|
||||
tree->leaf_count--;
|
||||
mark_inode_dirty(tree->inode);
|
||||
}
|
||||
@@ -205,7 +221,9 @@ again:
|
||||
hfs_bnode_put(node);
|
||||
node = fd->bnode = parent;
|
||||
|
||||
__hfs_brec_find(node, fd, hfs_find_rec_by_key);
|
||||
res = __hfs_brec_find(node, fd, hfs_find_rec_by_key);
|
||||
if (res && res != -ENOENT)
|
||||
return res;
|
||||
goto again;
|
||||
}
|
||||
hfs_bnode_write_u16(node,
|
||||
@@ -368,6 +386,7 @@ static int hfs_brec_update_parent(struct hfs_find_data *fd)
|
||||
int newkeylen, diff;
|
||||
int rec, rec_off, end_rec_off;
|
||||
int start_off, end_off;
|
||||
int res;
|
||||
|
||||
tree = fd->tree;
|
||||
node = fd->bnode;
|
||||
@@ -379,7 +398,9 @@ again:
|
||||
parent = hfs_bnode_find(tree, node->parent);
|
||||
if (IS_ERR(parent))
|
||||
return PTR_ERR(parent);
|
||||
__hfs_brec_find(parent, fd, hfs_find_rec_by_key);
|
||||
res = __hfs_brec_find(parent, fd, hfs_find_rec_by_key);
|
||||
if (res && res != -ENOENT)
|
||||
return res;
|
||||
if (fd->record < 0)
|
||||
return -ENOENT;
|
||||
hfs_bnode_dump(parent);
|
||||
|
||||
+14
-2
@@ -168,8 +168,8 @@ static struct page *hfs_bmap_get_map_page(struct hfs_bnode *node,
|
||||
}
|
||||
|
||||
ctx->len = hfs_brec_lenoff(node, rec_idx, &off16);
|
||||
if (!ctx->len)
|
||||
return ERR_PTR(-ENOENT);
|
||||
if (hfs_brec_len_invalid(node, ctx->len))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (!is_bnode_offset_valid(node, off16))
|
||||
return ERR_PTR(-EIO);
|
||||
@@ -622,6 +622,12 @@ void hfs_bmap_free(struct hfs_bnode *node)
|
||||
if (IS_ERR(node))
|
||||
return;
|
||||
len = hfs_brec_lenoff(node, 2, &off);
|
||||
if (hfs_brec_len_invalid(node, len)) {
|
||||
pr_err("invalid bmap record length: node %u, len %u\n",
|
||||
node->this, len);
|
||||
hfs_bnode_put(node);
|
||||
return;
|
||||
}
|
||||
while (nidx >= len * 8) {
|
||||
u32 i;
|
||||
|
||||
@@ -648,6 +654,12 @@ void hfs_bmap_free(struct hfs_bnode *node)
|
||||
return;
|
||||
}
|
||||
len = hfs_brec_lenoff(node, 0, &off);
|
||||
if (hfs_brec_len_invalid(node, len)) {
|
||||
pr_err("invalid bmap record length: node %u, len %u\n",
|
||||
node->this, len);
|
||||
hfs_bnode_put(node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res = hfs_bmap_clear_bit(node, nidx);
|
||||
|
||||
+12
-13
@@ -204,7 +204,7 @@ int hfsplus_find_cat(struct super_block *sb, u32 cnid,
|
||||
return err;
|
||||
|
||||
type = be16_to_cpu(tmp.type);
|
||||
if (type != HFSPLUS_FOLDER_THREAD && type != HFSPLUS_FILE_THREAD) {
|
||||
if (!is_hfs_thread_record_type(type)) {
|
||||
pr_err("found bad thread record in catalog\n");
|
||||
return -EIO;
|
||||
}
|
||||
@@ -350,23 +350,22 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str)
|
||||
goto out;
|
||||
|
||||
if (!str) {
|
||||
int len;
|
||||
hfsplus_cat_entry entry = {0};
|
||||
|
||||
hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid);
|
||||
err = hfs_brec_find(&fd, hfs_find_rec_by_key);
|
||||
err = hfsplus_brec_read_cat(&fd, &entry);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
off = fd.entryoffset +
|
||||
offsetof(struct hfsplus_cat_thread, nodeName);
|
||||
fd.search_key->cat.parent = cpu_to_be32(dir->i_ino);
|
||||
hfs_bnode_read(fd.bnode,
|
||||
&fd.search_key->cat.name.length, off, 2);
|
||||
len = be16_to_cpu(fd.search_key->cat.name.length) * 2;
|
||||
hfs_bnode_read(fd.bnode,
|
||||
&fd.search_key->cat.name.unicode,
|
||||
off + 2, len);
|
||||
fd.search_key->key_len = cpu_to_be16(6 + len);
|
||||
type = be16_to_cpu(entry.type);
|
||||
if (!is_hfs_thread_record_type(type)) {
|
||||
pr_err("found bad thread record in catalog\n");
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
hfsplus_cat_build_key_uni(fd.search_key, dir->i_ino,
|
||||
&entry.thread.nodeName);
|
||||
} else {
|
||||
err = hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str);
|
||||
if (unlikely(err))
|
||||
|
||||
@@ -110,6 +110,8 @@ static int __hfsplus_ext_write_extent(struct inode *inode,
|
||||
} else {
|
||||
if (res)
|
||||
return res;
|
||||
if (fd->entrylength != sizeof(hfsplus_extent_rec))
|
||||
return -EIO;
|
||||
hfs_bnode_write(fd->bnode, hip->cached_extents,
|
||||
fd->entryoffset, fd->entrylength);
|
||||
hip->extent_state &= ~HFSPLUS_EXT_DIRTY;
|
||||
|
||||
@@ -521,6 +521,12 @@ static inline u32 hfsplus_cat_thread_size(const struct hfsplus_cat_thread *threa
|
||||
be16_to_cpu(thread->nodeName.length) * sizeof(hfsplus_unichr);
|
||||
}
|
||||
|
||||
static inline
|
||||
bool is_hfs_thread_record_type(u16 type)
|
||||
{
|
||||
return type == HFSPLUS_FOLDER_THREAD || type == HFSPLUS_FILE_THREAD;
|
||||
}
|
||||
|
||||
int hfsplus_brec_read_cat(struct hfs_find_data *fd, hfsplus_cat_entry *entry);
|
||||
|
||||
/*
|
||||
@@ -587,6 +593,85 @@ bool is_bnode_offset_valid(struct hfs_bnode *node, u32 off)
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
static inline
|
||||
bool hfs_bnode_num_recs_invalid(struct hfs_bnode *node)
|
||||
{
|
||||
u32 node_size;
|
||||
u32 table_size;
|
||||
u32 area_size;
|
||||
u32 rec_size = sizeof(__be16);
|
||||
u32 desc_size = sizeof(struct hfs_bnode_desc);
|
||||
|
||||
if (!node || !node->tree)
|
||||
return true;
|
||||
|
||||
node_size = node->tree->node_size;
|
||||
if (node_size < desc_size)
|
||||
return true;
|
||||
|
||||
area_size = node_size - desc_size;
|
||||
table_size = ((u32)node->num_recs + 1) * rec_size;
|
||||
|
||||
return table_size > area_size;
|
||||
}
|
||||
|
||||
static inline
|
||||
bool hfs_brec_record_invalid(struct hfs_bnode *node, int record)
|
||||
{
|
||||
if (hfs_bnode_num_recs_invalid(node))
|
||||
return true;
|
||||
if (record < 0)
|
||||
return true;
|
||||
|
||||
return record >= node->num_recs;
|
||||
}
|
||||
|
||||
static inline
|
||||
bool hfs_brec_offsets_invalid(struct hfs_bnode *node, u16 off, u16 next_off)
|
||||
{
|
||||
u32 table_size;
|
||||
u32 table_start;
|
||||
u32 rec_size = sizeof(__be16);
|
||||
u32 desc_size = sizeof(struct hfs_bnode_desc);
|
||||
|
||||
if (!node || !node->tree)
|
||||
return true;
|
||||
|
||||
if (off < desc_size || (off & 1))
|
||||
return true;
|
||||
|
||||
if (next_off <= off ||
|
||||
next_off > node->tree->node_size ||
|
||||
(next_off & 1))
|
||||
return true;
|
||||
|
||||
table_size = ((u32)node->num_recs + 1) * rec_size;
|
||||
table_start = node->tree->node_size - table_size;
|
||||
if (next_off > table_start)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline
|
||||
bool hfs_brec_len_invalid(struct hfs_bnode *node, u16 len)
|
||||
{
|
||||
if (!node || !node->tree)
|
||||
return true;
|
||||
|
||||
return len == 0 || len > node->tree->node_size;
|
||||
}
|
||||
|
||||
static inline
|
||||
void hfs_find_result_init(struct hfs_find_data *fd)
|
||||
{
|
||||
fd->record = -1;
|
||||
fd->keyoffset = -1;
|
||||
fd->keylength = -1;
|
||||
fd->entryoffset = -1;
|
||||
fd->entrylength = -1;
|
||||
}
|
||||
|
||||
static inline
|
||||
u32 check_and_correct_requested_length(struct hfs_bnode *node, u32 off, u32 len)
|
||||
{
|
||||
|
||||
+9
-1
@@ -43,11 +43,19 @@ int hfsplus_write_begin(const struct kiocb *iocb,
|
||||
unsigned len, struct folio **foliop,
|
||||
void **fsdata)
|
||||
{
|
||||
struct inode *inode = mapping->host;
|
||||
struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
|
||||
loff_t total_capacity;
|
||||
int ret;
|
||||
|
||||
total_capacity = (loff_t)sbi->total_blocks << sbi->alloc_blksz_shift;
|
||||
|
||||
if (pos >= total_capacity)
|
||||
return -EFBIG;
|
||||
|
||||
ret = cont_write_begin(iocb, mapping, pos, len, foliop, fsdata,
|
||||
hfsplus_get_block,
|
||||
&HFSPLUS_I(mapping->host)->phys_size);
|
||||
&HFSPLUS_I(inode)->phys_size);
|
||||
if (unlikely(ret))
|
||||
hfsplus_write_failed(mapping, pos + len);
|
||||
|
||||
|
||||
@@ -39,8 +39,7 @@ static struct test_mock_string_env *setup_mock_str_env(u32 buf_size)
|
||||
|
||||
static void free_mock_str_env(struct test_mock_string_env *env)
|
||||
{
|
||||
if (env->buf)
|
||||
kfree(env->buf);
|
||||
kfree(env->buf);
|
||||
kfree(env);
|
||||
}
|
||||
|
||||
|
||||
@@ -510,14 +510,21 @@ struct hfs_btree_header_rec {
|
||||
b-tree but not in extents
|
||||
b-tree (hfsplus). */
|
||||
|
||||
/* HFS BTree misc info */
|
||||
#define HFS_TREE_HEAD 0
|
||||
#define HFS_BTREE_HDR_MAP_REC_INDEX 2 /* Map (bitmap) record in Header node */
|
||||
#define HFS_BTREE_MAP_NODE_REC_INDEX 0 /* Map record in Map Node */
|
||||
|
||||
/* HFS+ BTree misc info */
|
||||
#define HFSPLUS_TREE_HEAD 0
|
||||
#define HFSPLUS_TREE_HEAD HFS_TREE_HEAD
|
||||
#define HFSPLUS_NODE_MXSZ 32768
|
||||
#define HFSPLUS_NODE_MINSZ 512
|
||||
#define HFSPLUS_ATTR_TREE_NODE_SIZE 8192
|
||||
#define HFSPLUS_BTREE_HDR_NODE_RECS_COUNT 3
|
||||
#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX 2 /* Map (bitmap) record in Header node */
|
||||
#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX 0 /* Map record in Map Node */
|
||||
/* Map (bitmap) record in Header node */
|
||||
#define HFSPLUS_BTREE_HDR_MAP_REC_INDEX HFS_BTREE_HDR_MAP_REC_INDEX
|
||||
/* Map record in Map Node */
|
||||
#define HFSPLUS_BTREE_MAP_NODE_REC_INDEX HFS_BTREE_MAP_NODE_REC_INDEX
|
||||
#define HFSPLUS_BTREE_HDR_USER_BYTES 128
|
||||
#define HFSPLUS_BTREE_MAP_NODE_RECS_COUNT 2
|
||||
#define HFSPLUS_BTREE_MAP_NODE_RESERVED_BYTES 2
|
||||
|
||||
Reference in New Issue
Block a user