When parse_subtree() is done with a node, it should release it with
free_node() instead of just free(). Otherwise the associated free space
bitmaps will leak, and the physical block will remain mapped.
I have been aware of this issue for a while, but memory leaks are not
particularly urgent for a program that will only run for a few minutes
at a time. There are still many structures I don't even attempt to
free. Much of this will need to be improved when we start parsing old
checkpoints.
Since in-memory catalog keys could point to memory-mapped on-disk names,
make sure they are copied into a memory buffer before releasing their
node. I'm not entirely pleased with this, but I can't think of a
cleaner solution without a big rewrite.
Fixes: a71899fe1353 ("apfsck: check order of keys in the container omap")
apfsck/btree.c | 24 +++++++++++++++++++-----
apfsck/key.c | 12 ++++++++++++
2 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/apfsck/btree.c b/apfsck/btree.c
index 0205907..a0a588d 100644
--- a/apfsck/btree.c
+++ b/apfsck/btree.c
@@ -527,8 +527,11 @@ static void parse_cat_record(void *key, void *val, int len)
* @last_key: parent key, that must come before all the keys in this subtree;
* on return, this will hold the last key of this subtree, that
* must come before the next key of the parent node
+ * @name_buf: buffer to store the name of @last_key when its node is freed
+ * (can be NULL if the keys have no name)
*/
-static void parse_subtree(struct node *root, struct key *last_key)
+static void parse_subtree(struct node *root,
+ struct key *last_key, char *name_buf)
{
struct btree *btree = root->btree;
struct key curr_key;
@@ -606,12 +609,22 @@ static void parse_subtree(struct node *root, struct key *last_key)
report("Object map",
"xid of node is older than xid of its child.");
- parse_subtree(child, last_key);
- free(child);
+ parse_subtree(child, last_key, name_buf);
+ node_free(child);
}
/* All records of @root are processed, so it's a good time for this */
node_compare_bmaps(root);
+
+ /*
+ * last_key->name is just a pointer to the memory-mapped on-disk name
+ * of the key. Since the caller will free the node, make a copy.
+ */
+ if (node_is_leaf(root) && last_key->name) {
+ assert(name_buf);
+ strcpy(name_buf, last_key->name);
+ last_key->name = name_buf;
+ }
}
/**
@@ -679,6 +692,7 @@ struct btree *parse_cat_btree(u64 oid, struct node *omap_root)
{
struct btree *cat;
struct key last_key = {0};
+ char name_buf[256];
cat = calloc(1, sizeof(*cat));
if (!cat) {
@@ -689,7 +703,7 @@ struct btree *parse_cat_btree(u64 oid, struct node *omap_root)
cat->omap_root = omap_root;
cat->root = read_node(oid, cat);
- parse_subtree(cat->root, &last_key);
+ parse_subtree(cat->root, &last_key, name_buf);
check_btree_footer(cat);
return cat;
@@ -723,7 +737,7 @@ struct btree *parse_omap_btree(u64 oid)
omap->omap_root = NULL; /* The omap doesn't have an omap of its own */
omap->root = read_node(le64_to_cpu(raw->om_tree_oid), omap);
- parse_subtree(omap->root, &last_key);
+ parse_subtree(omap->root, &last_key, NULL /* name_buf */);
check_btree_footer(omap);
return omap;
diff --git a/apfsck/key.c b/apfsck/key.c
index 0d7b290..25865eb 100644
--- a/apfsck/key.c
+++ b/apfsck/key.c
@@ -111,6 +111,10 @@ static void read_dir_rec_key(void *raw, int size, struct key *key)
report("Directory record", "filename hash is corrupted.");
namelen = le32_to_cpu(raw_key->name_len_and_hash) & 0x3FFU;
+ if (namelen > 256) {
+ /* The name must fit in name_buf from parse_subtree() */
+ report("Directory record", "name is too long.");
+ }
if (strlen(key->name) + 1 != namelen) {
/* APFS counts the NULL termination for the filename length */
report("Directory record", "wrong name length in key.");
@@ -142,6 +146,10 @@ static void read_xattr_key(void *raw, int size, struct key *key)
key->name = (char *)raw_key->name;
namelen = le16_to_cpu(raw_key->name_len);
+ if (namelen > 256) {
+ /* The name must fit in name_buf from parse_subtree() */
+ report("Xattr record", "name is too long.");
+ }
if (strlen(key->name) + 1 != namelen) {
/* APFS counts the NULL termination in the string length */
report("Xattr record", "wrong name length.");
@@ -176,6 +184,10 @@ static void read_snap_name_key(void *raw, int size, struct key *key)
key->name = (char *)raw_key->name;
namelen = le16_to_cpu(raw_key->name_len);
+ if (namelen > 256) {
+ /* The name must fit in name_buf from parse_subtree() */
+ report("Snapshot name record", "name is too long.");
+ }
if (strlen(key->name) + 1 != namelen) {
/* APFS counts the NULL termination in the string length */
report("Snapshot name record", "wrong name length.");
--
2.11.0