Skip to content

Commit

Permalink
ubifs: Limit number of xattrs per inode
Browse files Browse the repository at this point in the history
Since we have to write one deletion inode per xattr
into the journal, limit the max number of xattrs.

In theory UBIFS supported up to 65535 xattrs per inode.
But this never worked correctly, expect no powercuts happened.
Now we support only as many xattrs as we can store in 50% of a
LEB.
Even for tiny flashes this allows dozens of xattrs per inode,
which is for an embedded filesystem still fine.

In case someone has existing inodes with much more xattrs, it is
still possible to delete them.
UBIFS will fall back to an non-atomic deletion mode.

Reported-by: Stefan Agner <stefan@agner.ch>
Fixes: 1e51764 ("UBIFS: add new flash file system")
Signed-off-by: Richard Weinberger <richard@nod.at>
  • Loading branch information
Richard Weinberger committed May 7, 2019
1 parent 988bec4 commit 9ca2d73
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 8 deletions.
15 changes: 14 additions & 1 deletion fs/ubifs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,10 @@ static int ubifs_unlink(struct inode *dir, struct dentry *dentry)
if (err)
return err;

err = ubifs_purge_xattrs(inode);
if (err)
return err;

sz_change = CALC_DENT_SIZE(fname_len(&nm));

ubifs_assert(c, inode_is_locked(dir));
Expand Down Expand Up @@ -900,6 +904,10 @@ static int ubifs_rmdir(struct inode *dir, struct dentry *dentry)
if (err)
return err;

err = ubifs_purge_xattrs(inode);
if (err)
return err;

sz_change = CALC_DENT_SIZE(fname_len(&nm));

err = ubifs_budget_space(c, &req);
Expand Down Expand Up @@ -1282,9 +1290,14 @@ static int do_rename(struct inode *old_dir, struct dentry *old_dentry,
old_dentry, old_inode->i_ino, old_dir->i_ino,
new_dentry, new_dir->i_ino, flags);

if (unlink)
if (unlink) {
ubifs_assert(c, inode_is_locked(new_inode));

err = ubifs_purge_xattrs(new_inode);
if (err)
return err;
}

if (unlink && is_dir) {
err = ubifs_check_dir_empty(new_inode);
if (err)
Expand Down
12 changes: 12 additions & 0 deletions fs/ubifs/journal.c
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,11 @@ int ubifs_jnl_write_inode(struct ubifs_info *c, const struct inode *inode)
struct inode *xino;
struct ubifs_dent_node *xent, *pxent = NULL;

if (ui->xattr_cnt >= ubifs_xattr_max_cnt(c)) {
ubifs_err(c, "Cannot delete inode, it has too much xattrs!");
goto out_release;
}

lowest_xent_key(c, &key, inode->i_ino);
while (1) {
xent = ubifs_tnc_next_ent(c, &key, &nm);
Expand All @@ -907,6 +912,13 @@ int ubifs_jnl_write_inode(struct ubifs_info *c, const struct inode *inode)
fname_len(&nm) = le16_to_cpu(xent->nlen);

xino = ubifs_iget(c->vfs_sb, xent->inum);
if (IS_ERR(xino)) {
err = PTR_ERR(xino);
ubifs_err(c, "dead directory entry '%s', error %d",
xent->name, err);
ubifs_ro_mode(c, err);
goto out_release;
}
ubifs_assert(c, ubifs_inode(xino)->xattr);

clear_nlink(xino);
Expand Down
8 changes: 8 additions & 0 deletions fs/ubifs/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@ static inline int ubifs_next_log_lnum(const struct ubifs_info *c, int lnum)
return lnum;
}

static inline int ubifs_xattr_max_cnt(struct ubifs_info *c)
{
int max_xattrs = (c->leb_size / 2) / UBIFS_INO_NODE_SZ;

ubifs_assert(c, max_xattrs < c->max_orphans);
return max_xattrs;
}

const char *ubifs_assert_action_name(struct ubifs_info *c);

#endif /* __UBIFS_MISC_H__ */
2 changes: 2 additions & 0 deletions fs/ubifs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -1548,6 +1548,8 @@ static int mount_ubifs(struct ubifs_info *c)
c->bud_bytes, c->bud_bytes >> 10, c->bud_bytes >> 20);
dbg_gen("max. seq. number: %llu", c->max_sqnum);
dbg_gen("commit number: %llu", c->cmt_no);
dbg_gen("max. xattrs per inode: %d", ubifs_xattr_max_cnt(c));
dbg_gen("max orphans: %d", c->max_orphans);

return 0;

Expand Down
1 change: 1 addition & 0 deletions fs/ubifs/ubifs.h
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,7 @@ int ubifs_xattr_set(struct inode *host, const char *name, const void *value,
size_t size, int flags, bool check_lock);
ssize_t ubifs_xattr_get(struct inode *host, const char *name, void *buf,
size_t size);
int ubifs_purge_xattrs(struct inode *host);

#ifdef CONFIG_UBIFS_FS_XATTR
void ubifs_evict_xattr_inode(struct ubifs_info *c, ino_t xattr_inum);
Expand Down
71 changes: 64 additions & 7 deletions fs/ubifs/xattr.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,6 @@
#include <linux/slab.h>
#include <linux/xattr.h>

/*
* Limit the number of extended attributes per inode so that the total size
* (@xattr_size) is guaranteeded to fit in an 'unsigned int'.
*/
#define MAX_XATTRS_PER_INODE 65535

/*
* Extended attribute type constants.
*
Expand Down Expand Up @@ -106,7 +100,7 @@ static int create_xattr(struct ubifs_info *c, struct inode *host,
.new_ino_d = ALIGN(size, 8), .dirtied_ino = 1,
.dirtied_ino_d = ALIGN(host_ui->data_len, 8) };

if (host_ui->xattr_cnt >= MAX_XATTRS_PER_INODE) {
if (host_ui->xattr_cnt >= ubifs_xattr_max_cnt(c)) {
ubifs_err(c, "inode %lu already has too many xattrs (%d), cannot create more",
host->i_ino, host_ui->xattr_cnt);
return -ENOSPC;
Expand Down Expand Up @@ -507,6 +501,69 @@ static int remove_xattr(struct ubifs_info *c, struct inode *host,
return err;
}

int ubifs_purge_xattrs(struct inode *host)
{
union ubifs_key key;
struct ubifs_info *c = host->i_sb->s_fs_info;
struct ubifs_dent_node *xent, *pxent = NULL;
struct inode *xino;
struct fscrypt_name nm = {0};
int err;

if (ubifs_inode(host)->xattr_cnt < ubifs_xattr_max_cnt(c))
return 0;

ubifs_warn(c, "inode %lu has too many xattrs, doing a non-atomic deletion",
host->i_ino);

lowest_xent_key(c, &key, host->i_ino);
while (1) {
xent = ubifs_tnc_next_ent(c, &key, &nm);
if (IS_ERR(xent)) {
err = PTR_ERR(xent);
break;
}

fname_name(&nm) = xent->name;
fname_len(&nm) = le16_to_cpu(xent->nlen);

xino = ubifs_iget(c->vfs_sb, xent->inum);
if (IS_ERR(xino)) {
err = PTR_ERR(xino);
ubifs_err(c, "dead directory entry '%s', error %d",
xent->name, err);
ubifs_ro_mode(c, err);
kfree(pxent);
return err;
}

ubifs_assert(c, ubifs_inode(xino)->xattr);

clear_nlink(xino);
err = remove_xattr(c, host, xino, &nm);
if (err) {
kfree(pxent);
iput(xino);
ubifs_err(c, "cannot remove xattr, error %d", err);
return err;
}

iput(xino);

kfree(pxent);
pxent = xent;
key_read(c, &xent->key, &key);
}

kfree(pxent);
if (err != -ENOENT) {
ubifs_err(c, "cannot find next direntry, error %d", err);
return err;
}

return 0;
}

/**
* ubifs_evict_xattr_inode - Evict an xattr inode.
* @c: UBIFS file-system description object
Expand Down

0 comments on commit 9ca2d73

Please sign in to comment.