/* * simple tests for the del-entry operations. * following the checks in vfs, plus the parent-child relationship. */ int au_may_del(struct dentry *dentry, aufs_bindex_t bindex, struct dentry *h_parent, int isdir) { int err; umode_t h_mode; struct dentry *h_dentry, *h_latest; struct inode *h_inode; h_dentry = au_h_dptr(dentry, bindex); if (d_really_is_positive(dentry)) { err = -ENOENT; if (unlikely(d_is_negative(h_dentry))) goto out; h_inode = d_inode(h_dentry); if (unlikely(!h_inode->i_nlink)) goto out; h_mode = h_inode->i_mode; if (!isdir) { err = -EISDIR; if (unlikely(S_ISDIR(h_mode))) goto out; } else if (unlikely(!S_ISDIR(h_mode))) { err = -ENOTDIR; goto out; } } else { /* rename(2) case */ err = -EIO; if (unlikely(d_is_positive(h_dentry))) goto out; } err = -ENOENT; /* expected parent dir is locked */ if (unlikely(h_parent != h_dentry->d_parent)) goto out; err = 0; /* * rmdir a dir may break the consistency on some filesystem. * let's try heavy test. */ err = -EACCES; if (unlikely(!au_opt_test(au_mntflags(dentry->d_sb), DIRPERM1) && au_test_h_perm(d_inode(h_parent), MAY_EXEC | MAY_WRITE))) goto out; h_latest = au_sio_lkup_one(&dentry->d_name, h_parent); err = -EIO; if (IS_ERR(h_latest)) goto out; if (h_latest == h_dentry) err = 0; dput(h_latest); out: return err; }
/* * Validate dentries for encrypted directories to make sure we aren't * potentially caching stale data after a key has been added or * removed. */ static int ext4_d_revalidate(struct dentry *dentry, unsigned int flags) { struct inode *dir = d_inode(dentry->d_parent); struct ext4_crypt_info *ci = EXT4_I(dir)->i_crypt_info; int dir_has_key, cached_with_key; if (!ext4_encrypted_inode(dir)) return 0; if (ci && ci->ci_keyring_key && (ci->ci_keyring_key->flags & ((1 << KEY_FLAG_INVALIDATED) | (1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_DEAD)))) ci = NULL; /* this should eventually be an flag in d_flags */ cached_with_key = dentry->d_fsdata != NULL; dir_has_key = (ci != NULL); /* * If the dentry was cached without the key, and it is a * negative dentry, it might be a valid name. We can't check * if the key has since been made available due to locking * reasons, so we fail the validation so ext4_lookup() can do * this check. * * We also fail the validation if the dentry was created with * the key present, but we no longer have the key, or vice versa. */ if ((!cached_with_key && d_is_negative(dentry)) || (!cached_with_key && dir_has_key) || (cached_with_key && !dir_has_key)) { #if 0 /* Revalidation debug */ char buf[80]; char *cp = simple_dname(dentry, buf, sizeof(buf)); if (IS_ERR(cp)) cp = (char *) "???"; pr_err("revalidate: %s %p %d %d %d\n", cp, dentry->d_fsdata, cached_with_key, d_is_negative(dentry), dir_has_key); #endif return 0; } return 1; }
int gr_acl_handle_rename(struct dentry *new_dentry, struct dentry *parent_dentry, const struct vfsmount *parent_mnt, struct dentry *old_dentry, struct inode *old_parent_inode, struct vfsmount *old_mnt, const struct filename *newname, unsigned int flags) { __u32 comp1, comp2; int error = 0; if (unlikely(!gr_acl_is_enabled())) return 0; if (flags & RENAME_EXCHANGE) { comp1 = gr_search_file(new_dentry, GR_READ | GR_WRITE | GR_AUDIT_READ | GR_AUDIT_WRITE | GR_SUPPRESS, parent_mnt); comp2 = gr_search_file(old_dentry, GR_READ | GR_WRITE | GR_AUDIT_READ | GR_AUDIT_WRITE | GR_SUPPRESS, old_mnt); } else if (d_is_negative(new_dentry)) { comp1 = gr_check_create(new_dentry, parent_dentry, parent_mnt, GR_READ | GR_WRITE | GR_CREATE | GR_AUDIT_READ | GR_AUDIT_WRITE | GR_AUDIT_CREATE | GR_SUPPRESS); comp2 = gr_search_file(old_dentry, GR_READ | GR_WRITE | GR_DELETE | GR_AUDIT_DELETE | GR_AUDIT_READ | GR_AUDIT_WRITE | GR_SUPPRESS, old_mnt); } else { comp1 = gr_search_file(new_dentry, GR_READ | GR_WRITE | GR_CREATE | GR_DELETE | GR_AUDIT_CREATE | GR_AUDIT_DELETE | GR_AUDIT_READ | GR_AUDIT_WRITE | GR_SUPPRESS, parent_mnt); comp2 = gr_search_file(old_dentry, GR_READ | GR_WRITE | GR_AUDIT_READ | GR_DELETE | GR_AUDIT_DELETE | GR_AUDIT_WRITE | GR_SUPPRESS, old_mnt); } if (RENAME_CHECK_SUCCESS(comp1, comp2) && ((comp1 & GR_AUDITS) || (comp2 & GR_AUDITS))) gr_log_fs_rbac_str(GR_DO_AUDIT, GR_RENAME_ACL_MSG, old_dentry, old_mnt, newname->name); else if (!RENAME_CHECK_SUCCESS(comp1, comp2) && !(comp1 & GR_SUPPRESS) && !(comp2 & GR_SUPPRESS)) { gr_log_fs_rbac_str(GR_DONT_AUDIT, GR_RENAME_ACL_MSG, old_dentry, old_mnt, newname->name); error = -EACCES; } else if (unlikely(!RENAME_CHECK_SUCCESS(comp1, comp2))) error = -EACCES; return error; }
/* * Validate dentries for encrypted directories to make sure we aren't * potentially caching stale data after a key has been added or * removed. */ static int fscrypt_d_revalidate(struct dentry *dentry, unsigned int flags) { struct dentry *dir; struct fscrypt_info *ci; int dir_has_key, cached_with_key; if (flags & LOOKUP_RCU) return -ECHILD; dir = dget_parent(dentry); if (!d_inode(dir)->i_sb->s_cop->is_encrypted(d_inode(dir))) { dput(dir); return 0; } ci = d_inode(dir)->i_crypt_info; if (ci && ci->ci_keyring_key && (ci->ci_keyring_key->flags & ((1 << KEY_FLAG_INVALIDATED) | (1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_DEAD)))) ci = NULL; /* this should eventually be an flag in d_flags */ spin_lock(&dentry->d_lock); cached_with_key = dentry->d_flags & DCACHE_ENCRYPTED_WITH_KEY; spin_unlock(&dentry->d_lock); dir_has_key = (ci != NULL); dput(dir); /* * If the dentry was cached without the key, and it is a * negative dentry, it might be a valid name. We can't check * if the key has since been made available due to locking * reasons, so we fail the validation so ext4_lookup() can do * this check. * * We also fail the validation if the dentry was created with * the key present, but we no longer have the key, or vice versa. */ if ((!cached_with_key && d_is_negative(dentry)) || (!cached_with_key && dir_has_key) || (cached_with_key && !dir_has_key)) return 0; return 1; }
__u32 gr_acl_handle_open(const struct dentry * dentry, const struct vfsmount * mnt, int acc_mode) { __u32 reqmode = GR_FIND; __u32 mode; if (unlikely(d_is_negative(dentry))) return reqmode; if (acc_mode & MAY_APPEND) reqmode |= GR_APPEND; else if (acc_mode & MAY_WRITE) reqmode |= GR_WRITE; if ((acc_mode & MAY_READ) && !d_is_dir(dentry)) reqmode |= GR_READ; mode = gr_search_file(dentry, reqmode | to_gr_audit(reqmode) | GR_SUPPRESS, mnt); if (unlikely(((mode & reqmode) == reqmode) && mode & GR_AUDITS)) { gr_log_fs_rbac_mode2(GR_DO_AUDIT, GR_OPEN_ACL_MSG, dentry, mnt, reqmode & GR_READ ? " reading" : "", reqmode & GR_WRITE ? " writing" : reqmode & GR_APPEND ? " appending" : ""); return reqmode; } else if (unlikely((mode & reqmode) != reqmode && !(mode & GR_SUPPRESS))) { gr_log_fs_rbac_mode2(GR_DONT_AUDIT, GR_OPEN_ACL_MSG, dentry, mnt, reqmode & GR_READ ? " reading" : "", reqmode & GR_WRITE ? " writing" : reqmode & GR_APPEND ? " appending" : ""); return 0; } else if (unlikely((mode & reqmode) != reqmode)) return 0; return reqmode; }
__u32 gr_acl_handle_hidden_file(const struct dentry * dentry, const struct vfsmount * mnt) { __u32 mode; if (unlikely(d_is_negative(dentry))) return GR_FIND; mode = gr_search_file(dentry, GR_FIND | GR_AUDIT_FIND | GR_SUPPRESS, mnt); if (unlikely(mode & GR_FIND && mode & GR_AUDIT_FIND)) { gr_log_fs_rbac_generic(GR_DO_AUDIT, GR_HIDDEN_ACL_MSG, dentry, mnt); return mode; } else if (unlikely(!(mode & GR_FIND) && !(mode & GR_SUPPRESS))) { gr_log_fs_rbac_generic(GR_DONT_AUDIT, GR_HIDDEN_ACL_MSG, dentry, mnt); return 0; } else if (unlikely(!(mode & GR_FIND))) return 0; return GR_FIND; }
/* * By adding a dirty branch, a cached dentry may be affected in various ways. * * a dirty branch is added * - on the top of layers * - in the middle of layers * - to the bottom of layers * * on the added branch there exists * - a whiteout * - a diropq * - a same named entry * + exist * * negative --> positive * * positive --> positive * - type is unchanged * - type is changed * + doesn't exist * * negative --> negative * * positive --> negative (rejected by au_br_del() for non-dir case) * - none */ static int au_refresh_by_dinfo(struct dentry *dentry, struct au_dinfo *dinfo, struct au_dinfo *tmp) { int err; aufs_bindex_t bindex, bend; struct { struct dentry *dentry; struct inode *inode; mode_t mode; } orig_h, tmp_h; struct au_hdentry *hd; struct inode *inode, *h_inode; struct dentry *h_dentry; err = 0; AuDebugOn(dinfo->di_bstart < 0); orig_h.mode = 0; orig_h.dentry = dinfo->di_hdentry[dinfo->di_bstart].hd_dentry; orig_h.inode = NULL; if (d_is_positive(orig_h.dentry)) { orig_h.inode = d_inode(orig_h.dentry); orig_h.mode = orig_h.inode->i_mode & S_IFMT; } memset(&tmp_h, 0, sizeof(tmp_h)); if (tmp->di_bstart >= 0) { tmp_h.dentry = tmp->di_hdentry[tmp->di_bstart].hd_dentry; tmp_h.inode = NULL; if (d_is_positive(tmp_h.dentry)) { tmp_h.inode = d_inode(tmp_h.dentry); tmp_h.mode = tmp_h.inode->i_mode & S_IFMT; } } inode = NULL; if (d_really_is_positive(dentry)) inode = d_inode(dentry); if (!orig_h.inode) { AuDbg("nagative originally\n"); if (inode) { au_hide(dentry); goto out; } AuDebugOn(inode); AuDebugOn(dinfo->di_bstart != dinfo->di_bend); AuDebugOn(dinfo->di_bdiropq != -1); if (!tmp_h.inode) { AuDbg("negative --> negative\n"); /* should have only one negative lower */ if (tmp->di_bstart >= 0 && tmp->di_bstart < dinfo->di_bstart) { AuDebugOn(tmp->di_bstart != tmp->di_bend); AuDebugOn(dinfo->di_bstart != dinfo->di_bend); au_set_h_dptr(dentry, dinfo->di_bstart, NULL); au_di_cp(dinfo, tmp); hd = tmp->di_hdentry + tmp->di_bstart; au_set_h_dptr(dentry, tmp->di_bstart, dget(hd->hd_dentry)); } au_dbg_verify_dinode(dentry); } else { AuDbg("negative --> positive\n"); /* * similar to the behaviour of creating with bypassing * aufs. * unhash it in order to force an error in the * succeeding create operation. * we should not set S_DEAD here. */ d_drop(dentry); /* au_di_swap(tmp, dinfo); */ au_dbg_verify_dinode(dentry); } } else { AuDbg("positive originally\n"); /* inode may be NULL */ AuDebugOn(inode && (inode->i_mode & S_IFMT) != orig_h.mode); if (!tmp_h.inode) { AuDbg("positive --> negative\n"); /* or bypassing aufs */ au_hide(dentry); if (tmp->di_bwh >= 0 && tmp->di_bwh <= dinfo->di_bstart) dinfo->di_bwh = tmp->di_bwh; if (inode) err = au_refresh_hinode_self(inode); au_dbg_verify_dinode(dentry); } else if (orig_h.mode == tmp_h.mode) { AuDbg("positive --> positive, same type\n"); if (!S_ISDIR(orig_h.mode) && dinfo->di_bstart > tmp->di_bstart) { /* * similar to the behaviour of removing and * creating. */ au_hide(dentry); if (inode) err = au_refresh_hinode_self(inode); au_dbg_verify_dinode(dentry); } else { /* fill empty slots */ if (dinfo->di_bstart > tmp->di_bstart) dinfo->di_bstart = tmp->di_bstart; if (dinfo->di_bend < tmp->di_bend) dinfo->di_bend = tmp->di_bend; dinfo->di_bwh = tmp->di_bwh; dinfo->di_bdiropq = tmp->di_bdiropq; hd = tmp->di_hdentry; bend = dinfo->di_bend; for (bindex = tmp->di_bstart; bindex <= bend; bindex++) { if (au_h_dptr(dentry, bindex)) continue; h_dentry = hd[bindex].hd_dentry; if (!h_dentry) continue; AuDebugOn(d_is_negative(h_dentry)); h_inode = d_inode(h_dentry); AuDebugOn(orig_h.mode != (h_inode->i_mode & S_IFMT)); au_set_h_dptr(dentry, bindex, dget(h_dentry)); } err = au_refresh_hinode(inode, dentry); au_dbg_verify_dinode(dentry); } } else { AuDbg("positive --> positive, different type\n"); /* similar to the behaviour of removing and creating */ au_hide(dentry); if (inode) err = au_refresh_hinode_self(inode); au_dbg_verify_dinode(dentry); } } out: return err; }
/* * returns positive/negative dentry, NULL or an error. * NULL means whiteout-ed or not-found. */ static struct dentry* au_do_lookup(struct dentry *h_parent, struct dentry *dentry, aufs_bindex_t bindex, struct qstr *wh_name, struct au_do_lookup_args *args) { struct dentry *h_dentry; struct inode *h_inode; struct au_branch *br; int wh_found, opq; unsigned char wh_able; const unsigned char allow_neg = !!au_ftest_lkup(args->flags, ALLOW_NEG); const unsigned char ignore_perm = !!au_ftest_lkup(args->flags, IGNORE_PERM); wh_found = 0; br = au_sbr(dentry->d_sb, bindex); wh_able = !!au_br_whable(br->br_perm); if (wh_able) wh_found = au_wh_test(h_parent, wh_name, /*try_sio*/0); h_dentry = ERR_PTR(wh_found); if (!wh_found) goto real_lookup; if (unlikely(wh_found < 0)) goto out; /* We found a whiteout */ /* au_set_dbend(dentry, bindex); */ au_set_dbwh(dentry, bindex); if (!allow_neg) return NULL; /* success */ real_lookup: if (!ignore_perm) h_dentry = vfsub_lkup_one(&dentry->d_name, h_parent); else h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent); if (IS_ERR(h_dentry)) { if (PTR_ERR(h_dentry) == -ENAMETOOLONG && !allow_neg) h_dentry = NULL; goto out; } h_inode = d_inode(h_dentry); if (d_is_negative(h_dentry)) { if (!allow_neg) goto out_neg; } else if (wh_found || (args->type && args->type != (h_inode->i_mode & S_IFMT))) goto out_neg; if (au_dbend(dentry) <= bindex) au_set_dbend(dentry, bindex); if (au_dbstart(dentry) < 0 || bindex < au_dbstart(dentry)) au_set_dbstart(dentry, bindex); au_set_h_dptr(dentry, bindex, h_dentry); if (!d_is_dir(h_dentry) || !wh_able || (d_really_is_positive(dentry) && !d_is_dir(dentry))) goto out; /* success */ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD); opq = au_diropq_test(h_dentry); mutex_unlock(&h_inode->i_mutex); if (opq > 0) au_set_dbdiropq(dentry, bindex); else if (unlikely(opq < 0)) { au_set_h_dptr(dentry, bindex, NULL); h_dentry = ERR_PTR(opq); } goto out; out_neg: dput(h_dentry); h_dentry = NULL; out: return h_dentry; }
/* * returns the number of lower positive dentries, * otherwise an error. * can be called at unlinking with @type is zero. */ int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type) { int npositive, err; aufs_bindex_t bindex, btail, bdiropq; unsigned char isdir, dirperm1; struct qstr whname; struct au_do_lookup_args args = { .flags = 0, .type = type }; const struct qstr *name = &dentry->d_name; struct dentry *parent; struct super_block *sb; sb = dentry->d_sb; err = au_test_shwh(sb, name); if (unlikely(err)) goto out; err = au_wh_name_alloc(&whname, name); if (unlikely(err)) goto out; isdir = !!d_is_dir(dentry); if (!type) au_fset_lkup(args.flags, ALLOW_NEG); dirperm1 = !!au_opt_test(au_mntflags(sb), DIRPERM1); npositive = 0; parent = dget_parent(dentry); btail = au_dbtaildir(parent); for (bindex = bstart; bindex <= btail; bindex++) { struct dentry *h_parent, *h_dentry; struct inode *h_inode, *h_dir; h_dentry = au_h_dptr(dentry, bindex); if (h_dentry) { if (d_is_positive(h_dentry)) npositive++; if (type != S_IFDIR) break; continue; } h_parent = au_h_dptr(parent, bindex); if (!h_parent || !d_is_dir(h_parent)) continue; h_dir = d_inode(h_parent); mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT); h_dentry = au_do_lookup(h_parent, dentry, bindex, &whname, &args); mutex_unlock(&h_dir->i_mutex); err = PTR_ERR(h_dentry); if (IS_ERR(h_dentry)) goto out_parent; if (h_dentry) au_fclr_lkup(args.flags, ALLOW_NEG); if (dirperm1) au_fset_lkup(args.flags, IGNORE_PERM); if (au_dbwh(dentry) >= 0) break; if (!h_dentry) continue; if (d_is_negative(h_dentry)) continue; h_inode = d_inode(h_dentry); npositive++; if (!args.type) args.type = h_inode->i_mode & S_IFMT; if (args.type != S_IFDIR) break; else if (isdir) { /* the type of lower may be different */ bdiropq = au_dbdiropq(dentry); if (bdiropq >= 0 && bdiropq <= bindex) break; } } if (npositive) { AuLabel(positive); au_update_dbstart(dentry); } err = npositive; if (unlikely(!au_opt_test(au_mntflags(sb), UDBA_NONE) && au_dbstart(dentry) < 0)) { err = -EIO; AuIOErr("both of real entry and whiteout found, %pd, err %d\n", dentry, err); } out_parent: dput(parent); kfree(whname.name); out: return err; } struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent) { struct dentry *dentry; int wkq_err; if (!au_test_h_perm_sio(d_inode(parent), MAY_EXEC)) dentry = vfsub_lkup_one(name, parent); else { struct vfsub_lkup_one_args args = { .errp = &dentry, .name = name, .parent = parent }; wkq_err = au_wkq_wait(vfsub_call_lkup_one, &args); if (unlikely(wkq_err)) dentry = ERR_PTR(wkq_err); } return dentry; } /* * lookup @dentry on @bindex which should be negative. */ int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex, int wh) { int err; struct dentry *parent, *h_parent, *h_dentry; struct au_branch *br; parent = dget_parent(dentry); h_parent = au_h_dptr(parent, bindex); br = au_sbr(dentry->d_sb, bindex); if (wh) h_dentry = au_whtmp_lkup(h_parent, br, &dentry->d_name); else h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent); err = PTR_ERR(h_dentry); if (IS_ERR(h_dentry)) goto out; if (unlikely(d_is_positive(h_dentry))) { err = -EIO; AuIOErr("%pd should be negative on b%d.\n", h_dentry, bindex); dput(h_dentry); goto out; } err = 0; if (bindex < au_dbstart(dentry)) au_set_dbstart(dentry, bindex); if (au_dbend(dentry) < bindex) au_set_dbend(dentry, bindex); au_set_h_dptr(dentry, bindex, h_dentry); out: dput(parent); return err; }