u-boot/fs/btrfs/subvolume.c
Dan Carpenter c331efd087 fs: btrfs: Prevent error pointer dereference in list_subvolums()
If btrfs_read_fs_root() fails with -ENOENT, then we go to the next
entry.  Fine.  But if it fails for a different reason then we need
to clean up and return an error code.  In the current code it
doesn't clean up but instead dereferences "root" and crashes.

Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
Reviewed-by: Marek Behún <kabel@kernel.org>
Reviewed-by: Qu Wenruo <wqu@suse.com>
2023-08-08 17:05:43 -04:00

236 lines
5.4 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* BTRFS filesystem implementation for U-Boot
*
* 2017 Marek Behún, CZ.NIC, kabel@kernel.org
*/
#include <malloc.h>
#include "ctree.h"
#include "btrfs.h"
#include "disk-io.h"
/*
* Resolve the path of ino inside subvolume @root into @path_ret.
*
* @path_ret must be at least PATH_MAX size.
*/
static int get_path_in_subvol(struct btrfs_root *root, u64 ino, char *path_ret)
{
struct btrfs_path path;
struct btrfs_key key;
char *tmp;
u64 cur = ino;
int ret = 0;
tmp = malloc(PATH_MAX);
if (!tmp)
return -ENOMEM;
tmp[0] = '\0';
btrfs_init_path(&path);
while (cur != BTRFS_FIRST_FREE_OBJECTID) {
struct btrfs_inode_ref *iref;
int name_len;
btrfs_release_path(&path);
key.objectid = cur;
key.type = BTRFS_INODE_REF_KEY;
key.offset = (u64)-1;
ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
/* Impossible */
if (ret == 0)
ret = -EUCLEAN;
if (ret < 0)
goto out;
ret = btrfs_previous_item(root, &path, cur,
BTRFS_INODE_REF_KEY);
if (ret > 0)
ret = -ENOENT;
if (ret < 0)
goto out;
strncpy(tmp, path_ret, PATH_MAX);
iref = btrfs_item_ptr(path.nodes[0], path.slots[0],
struct btrfs_inode_ref);
name_len = btrfs_inode_ref_name_len(path.nodes[0],
iref);
if (name_len > BTRFS_NAME_LEN) {
ret = -ENAMETOOLONG;
goto out;
}
read_extent_buffer(path.nodes[0], path_ret,
(unsigned long)(iref + 1), name_len);
path_ret[name_len] = '/';
path_ret[name_len + 1] = '\0';
strncat(path_ret, tmp, PATH_MAX);
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
cur = key.offset;
}
out:
btrfs_release_path(&path);
free(tmp);
return ret;
}
static int list_one_subvol(struct btrfs_root *root, char *path_ret)
{
struct btrfs_fs_info *fs_info = root->fs_info;
struct btrfs_root *tree_root = fs_info->tree_root;
struct btrfs_path path;
struct btrfs_key key;
char *tmp;
u64 cur = root->root_key.objectid;
int ret = 0;
tmp = malloc(PATH_MAX);
if (!tmp)
return -ENOMEM;
tmp[0] = '\0';
path_ret[0] = '\0';
btrfs_init_path(&path);
while (cur != BTRFS_FS_TREE_OBJECTID) {
struct btrfs_root_ref *rr;
struct btrfs_key location;
int name_len;
u64 ino;
key.objectid = cur;
key.type = BTRFS_ROOT_BACKREF_KEY;
key.offset = (u64)-1;
btrfs_release_path(&path);
ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0);
if (ret == 0)
ret = -EUCLEAN;
if (ret < 0)
goto out;
ret = btrfs_previous_item(tree_root, &path, cur,
BTRFS_ROOT_BACKREF_KEY);
if (ret > 0)
ret = -ENOENT;
if (ret < 0)
goto out;
/* Get the subvolume name */
rr = btrfs_item_ptr(path.nodes[0], path.slots[0],
struct btrfs_root_ref);
strncpy(tmp, path_ret, PATH_MAX);
name_len = btrfs_root_ref_name_len(path.nodes[0], rr);
if (name_len > BTRFS_NAME_LEN) {
ret = -ENAMETOOLONG;
goto out;
}
ino = btrfs_root_ref_dirid(path.nodes[0], rr);
read_extent_buffer(path.nodes[0], path_ret,
(unsigned long)(rr + 1), name_len);
path_ret[name_len] = '/';
path_ret[name_len + 1] = '\0';
strncat(path_ret, tmp, PATH_MAX);
/* Get the path inside the parent subvolume */
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
location.objectid = key.offset;
location.type = BTRFS_ROOT_ITEM_KEY;
location.offset = (u64)-1;
root = btrfs_read_fs_root(fs_info, &location);
if (IS_ERR(root)) {
ret = PTR_ERR(root);
goto out;
}
ret = get_path_in_subvol(root, ino, path_ret);
if (ret < 0)
goto out;
cur = key.offset;
}
/* Add the leading '/' */
strncpy(tmp, path_ret, PATH_MAX);
strncpy(path_ret, "/", PATH_MAX);
strncat(path_ret, tmp, PATH_MAX);
out:
btrfs_release_path(&path);
free(tmp);
return ret;
}
static int list_subvolums(struct btrfs_fs_info *fs_info)
{
struct btrfs_root *tree_root = fs_info->tree_root;
struct btrfs_root *root;
struct btrfs_path path;
struct btrfs_key key;
char *result;
int ret = 0;
result = malloc(PATH_MAX);
if (!result)
return -ENOMEM;
ret = list_one_subvol(fs_info->fs_root, result);
if (ret < 0)
goto out;
root = fs_info->fs_root;
printf("ID %llu gen %llu path %.*s\n",
root->root_key.objectid, btrfs_root_generation(&root->root_item),
PATH_MAX, result);
key.objectid = BTRFS_FIRST_FREE_OBJECTID;
key.type = BTRFS_ROOT_ITEM_KEY;
key.offset = 0;
btrfs_init_path(&path);
ret = btrfs_search_slot(NULL, tree_root, &key, &path, 0, 0);
if (ret < 0)
goto out;
while (1) {
if (path.slots[0] >= btrfs_header_nritems(path.nodes[0]))
goto next;
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
if (key.objectid > BTRFS_LAST_FREE_OBJECTID)
break;
if (key.objectid < BTRFS_FIRST_FREE_OBJECTID ||
key.type != BTRFS_ROOT_ITEM_KEY)
goto next;
key.offset = (u64)-1;
root = btrfs_read_fs_root(fs_info, &key);
if (IS_ERR(root)) {
ret = PTR_ERR(root);
if (ret == -ENOENT)
goto next;
goto out;
}
ret = list_one_subvol(root, result);
if (ret < 0)
goto out;
printf("ID %llu gen %llu path %.*s\n",
root->root_key.objectid,
btrfs_root_generation(&root->root_item),
PATH_MAX, result);
next:
ret = btrfs_next_item(tree_root, &path);
if (ret < 0)
goto out;
if (ret > 0) {
ret = 0;
break;
}
}
out:
free(result);
return ret;
}
void btrfs_list_subvols(void)
{
struct btrfs_fs_info *fs_info = current_fs_info;
int ret;
if (!fs_info)
return;
ret = list_subvolums(fs_info);
if (ret < 0)
error("failed to list subvolume: %d", ret);
}