ubifs: Fix unlink code wrt. double hash lookups

When removing an encrypted file with a long name and without having
the key we have to be able to locate and remove the directory entry
via a double hash. This corner case was simply forgotten.

Fixes: 528e3d178f ("ubifs: Add full hash lookup support")
Reported-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Signed-off-by: Richard Weinberger <richard@nod.at>
This commit is contained in:
Richard Weinberger 2017-05-17 10:36:46 +02:00
parent 59a74990f8
commit 781f675e2d
3 changed files with 122 additions and 29 deletions

View file

@ -585,7 +585,10 @@ int ubifs_jnl_update(struct ubifs_info *c, const struct inode *dir,
if (!xent) {
dent->ch.node_type = UBIFS_DENT_NODE;
dent_key_init(c, &dent_key, dir->i_ino, nm);
if (nm->hash)
dent_key_init_hash(c, &dent_key, dir->i_ino, nm->hash);
else
dent_key_init(c, &dent_key, dir->i_ino, nm);
} else {
dent->ch.node_type = UBIFS_XENT_NODE;
xent_key_init(c, &dent_key, dir->i_ino, nm);
@ -629,7 +632,10 @@ int ubifs_jnl_update(struct ubifs_info *c, const struct inode *dir,
kfree(dent);
if (deletion) {
err = ubifs_tnc_remove_nm(c, &dent_key, nm);
if (nm->hash)
err = ubifs_tnc_remove_dh(c, &dent_key, nm->minor_hash);
else
err = ubifs_tnc_remove_nm(c, &dent_key, nm);
if (err)
goto out_ro;
err = ubifs_add_dirt(c, lnum, dlen);

View file

@ -1880,13 +1880,53 @@ int ubifs_tnc_lookup_nm(struct ubifs_info *c, const union ubifs_key *key,
return do_lookup_nm(c, key, node, nm);
}
static int search_dh_cookie(struct ubifs_info *c, const union ubifs_key *key,
struct ubifs_dent_node *dent, uint32_t cookie,
struct ubifs_znode **zn, int *n)
{
int err;
struct ubifs_znode *znode = *zn;
struct ubifs_zbranch *zbr;
union ubifs_key *dkey;
for (;;) {
if (!err) {
err = tnc_next(c, &znode, n);
if (err)
goto out;
}
zbr = &znode->zbranch[*n];
dkey = &zbr->key;
if (key_inum(c, dkey) != key_inum(c, key) ||
key_type(c, dkey) != key_type(c, key)) {
err = -ENOENT;
goto out;
}
err = tnc_read_hashed_node(c, zbr, dent);
if (err)
goto out;
if (key_hash(c, key) == key_hash(c, dkey) &&
le32_to_cpu(dent->cookie) == cookie) {
*zn = znode;
goto out;
}
}
out:
return err;
}
static int do_lookup_dh(struct ubifs_info *c, const union ubifs_key *key,
struct ubifs_dent_node *dent, uint32_t cookie)
{
int n, err, type = key_type(c, key);
int n, err;
struct ubifs_znode *znode;
struct ubifs_zbranch *zbr;
union ubifs_key *dkey, start_key;
union ubifs_key start_key;
ubifs_assert(is_hash_key(c, key));
@ -1897,30 +1937,7 @@ static int do_lookup_dh(struct ubifs_info *c, const union ubifs_key *key,
if (unlikely(err < 0))
goto out_unlock;
for (;;) {
if (!err) {
err = tnc_next(c, &znode, &n);
if (err)
goto out_unlock;
}
zbr = &znode->zbranch[n];
dkey = &zbr->key;
if (key_inum(c, dkey) != key_inum(c, key) ||
key_type(c, dkey) != type) {
err = -ENOENT;
goto out_unlock;
}
err = tnc_read_hashed_node(c, zbr, dent);
if (err)
goto out_unlock;
if (key_hash(c, key) == key_hash(c, dkey) &&
le32_to_cpu(dent->cookie) == cookie)
goto out_unlock;
}
err = search_dh_cookie(c, key, dent, cookie, &znode, &n);
out_unlock:
mutex_unlock(&c->tnc_mutex);
@ -2662,6 +2679,74 @@ int ubifs_tnc_remove_nm(struct ubifs_info *c, const union ubifs_key *key,
return err;
}
/**
* ubifs_tnc_remove_dh - remove an index entry for a "double hashed" node.
* @c: UBIFS file-system description object
* @key: key of node
* @cookie: node cookie for collision resolution
*
* Returns %0 on success or negative error code on failure.
*/
int ubifs_tnc_remove_dh(struct ubifs_info *c, const union ubifs_key *key,
uint32_t cookie)
{
int n, err;
struct ubifs_znode *znode;
struct ubifs_dent_node *dent;
struct ubifs_zbranch *zbr;
if (!c->double_hash)
return -EOPNOTSUPP;
mutex_lock(&c->tnc_mutex);
err = lookup_level0_dirty(c, key, &znode, &n);
if (err <= 0)
goto out_unlock;
zbr = &znode->zbranch[n];
dent = kmalloc(UBIFS_MAX_DENT_NODE_SZ, GFP_NOFS);
if (!dent) {
err = -ENOMEM;
goto out_unlock;
}
err = tnc_read_hashed_node(c, zbr, dent);
if (err)
goto out_free;
/* If the cookie does not match, we're facing a hash collision. */
if (le32_to_cpu(dent->cookie) != cookie) {
union ubifs_key start_key;
lowest_dent_key(c, &start_key, key_inum(c, key));
err = ubifs_lookup_level0(c, &start_key, &znode, &n);
if (unlikely(err < 0))
goto out_free;
err = search_dh_cookie(c, key, dent, cookie, &znode, &n);
if (err)
goto out_free;
}
if (znode->cnext || !ubifs_zn_dirty(znode)) {
znode = dirty_cow_bottom_up(c, znode);
if (IS_ERR(znode)) {
err = PTR_ERR(znode);
goto out_free;
}
}
err = tnc_delete(c, znode, n);
out_free:
kfree(dent);
out_unlock:
if (!err)
err = dbg_check_tnc(c, 0);
mutex_unlock(&c->tnc_mutex);
return err;
}
/**
* key_in_range - determine if a key falls within a range of keys.
* @c: UBIFS file-system description object

View file

@ -1589,6 +1589,8 @@ int ubifs_tnc_add_nm(struct ubifs_info *c, const union ubifs_key *key,
int ubifs_tnc_remove(struct ubifs_info *c, const union ubifs_key *key);
int ubifs_tnc_remove_nm(struct ubifs_info *c, const union ubifs_key *key,
const struct fscrypt_name *nm);
int ubifs_tnc_remove_dh(struct ubifs_info *c, const union ubifs_key *key,
uint32_t cookie);
int ubifs_tnc_remove_range(struct ubifs_info *c, union ubifs_key *from_key,
union ubifs_key *to_key);
int ubifs_tnc_remove_ino(struct ubifs_info *c, ino_t inum);