backend: respect the BOOKMARK_DELETE_DIR flag

Following commit 2e3685f217,
make sure all backends check this flag and return correct error codes.

Normally this is not mandatory, since the kernel looks up
the directory entry to be removed, and fails if the system call
is inappropriate (e.g., calling rmdir() on a regular file).
This happens before FUSE_UNLINK or FUSE_RMDIR is sent to the server.

However, when not in exclusive mode, there is a short window that
TOCTOU problem may occur, which may lead to undesired behavior
(e.g., deletion of a non-empty directory) or even the corruption of
bookmark storage if not properly checked.

Also explain this flag in the user manual.
This commit is contained in:
CismonX 2025-03-27 12:36:27 +08:00
parent 86c7af8f6f
commit bdfa812d79
No known key found for this signature in database
GPG key ID: 3094873E29A482FB
3 changed files with 20 additions and 8 deletions

View file

@ -2808,6 +2808,13 @@ depending on the value of @code{flags}.
A bit array of the following flags:
@table @code
@item BOOKMARKFS_BOOKMARK_DELETE_DIR
Indicates that the object to be removed is a directory.
If the current backend context is not in exclusive mode
(@pxref{Exclusive Mode}), this information may be incorrect.
If so, the function should fail with @code{ENOTDIR} or @code{EISDIR}.
@item BOOKMARKFS_BOOKMARK_TYPE_MASK
The subsystem to operate on.

View file

@ -2194,7 +2194,7 @@ bookmark_delete (
void *backend_ctx,
uint64_t parent_id,
char const *name,
uint32_t UNUSED_VAR(flags)
uint32_t flags
) {
struct backend_ctx *ctx = backend_ctx;
debug_assert(!(ctx->flags & BOOKMARKFS_BACKEND_READONLY));
@ -2223,15 +2223,19 @@ bookmark_delete (
if (0 != lookup_name(ctx, parent_id, name, strlen(name), &lctx, &entry)) {
return -ENOENT;
};
json_t *siblings = parent_entry->children;
json_t const *node = entry->node;
// Check if entry can be deleted
json_t const *children = json_object_sget(node, "children");
if (children != NULL) {
if (0 != json_array_size(children)) {
if (flags & BOOKMARK_FLAG(DELETE_DIR)) {
if (unlikely(entry->children == NULL)) {
return -ENOTDIR;
}
if (0 != json_array_size(entry->children)) {
return -ENOTEMPTY;
}
} else {
if (unlikely(entry->children != NULL)) {
return -EISDIR;
}
}
// Remove from maps
@ -2245,7 +2249,8 @@ bookmark_delete (
free(entry);
// Remove from store
json_array_remove(siblings, json_array_search(siblings, node));
json_t *siblings = parent_entry->children;
json_array_remove(siblings, json_array_search(siblings, entry->node));
ctx->dirty = DIRTY_LEVEL_DATA;

View file

@ -454,7 +454,7 @@ bookmark_do_delete (
return status;
}
if (unlikely((cols.place_id == 0) != is_dir)) {
return is_dir ? -ENOTDIR : -EPERM;
return is_dir ? -ENOTDIR : -EISDIR;
}
status = mozbm_delete(ctx, cols.id, is_dir);