@@ -11,6 +11,7 @@
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <linux/dcache.h>
+#include <linux/namei.h>
#include "glob.h"
#include "vfs_cache.h"
@@ -1701,38 +1702,98 @@
int ksmbd_validate_name_reconnect(struct ksmbd_share_config *share,
struct ksmbd_file *fp, char *name)
{
- char *pathname, *ab_pathname;
+ const struct path *root = &share->vfs_path;
+ struct path cur;
+ const char *p, *end;
+ unsigned int seq, attempts = 0, hops;
int ret = 0;
- pathname = kmalloc(PATH_MAX, KSMBD_DEFAULT_GFP);
- if (!pathname)
- return -EACCES;
-
- ab_pathname = d_path(&fp->filp->f_path, pathname, PATH_MAX);
- if (IS_ERR(ab_pathname)) {
- kfree(pathname);
- return -EACCES;
- }
-
- if (name) {
- size_t len = strlen(ab_pathname);
-
- if (len == share->path_sz && !strncmp(ab_pathname, share->path, len)) {
- /* the durable fp is the share root itself */
- if (name[0])
+ if (!name)
+ return 0;
+ if (name[0] == '/' || d_unlinked(fp->filp->f_path.dentry))
+ return -EINVAL;
+
+ for (;;) {
+ if (++attempts > 16)
+ return -EAGAIN;
+ ret = 0;
+ p = name;
+ end = name + strlen(name);
+ cur = fp->filp->f_path;
+ path_get(&cur);
+ seq = read_seqbegin(&rename_lock);
+ hops = 0;
+ for (;;) {
+ const char *comp;
+ size_t clen;
+ struct dentry *parent;
+
+ /* Bound the walk so mount/rename storms cannot spin forever. */
+ if (++hops > 8192) {
+ ret = -EAGAIN;
+ break;
+ }
+
+ if (cur.dentry == root->dentry &&
+ cur.mnt == root->mnt) {
+ /* the share root itself only matches an empty name */
+ if (end != p)
+ ret = -EINVAL;
+ break;
+ }
+
+ if (cur.dentry == cur.mnt->mnt_root) {
+ /*
+ * Mount root: hop into the parent mount and
+ * let its mountpoint supply the component.
+ * Mount topology can change mid-walk
+ * (mount_lock is not available here), but
+ * racing that needs local mount privileges.
+ */
+ if (!follow_up(&cur)) {
+ ret = -EINVAL;
+ break;
+ }
+ continue;
+ }
+
+ for (comp = end; comp > p && comp[-1] != '/'; comp--)
+ ;
+ clen = end - comp;
+ if (!clen) {
+ /* name exhausted while the fp is deeper, or empty component */
ret = -EINVAL;
- } else if (len <= share->path_sz ||
- strncmp(ab_pathname, share->path, share->path_sz) ||
- ab_pathname[share->path_sz] != '/' ||
- strcmp(&ab_pathname[share->path_sz + 1], name)) {
- ret = -EINVAL;
- }
- if (ret)
- ksmbd_debug(SMB, "invalid name reconnect %s\n", name);
- }
+ break;
+ }
- kfree(pathname);
+ spin_lock(&cur.dentry->d_lock);
+ if (cur.dentry->d_name.len != clen ||
+ memcmp(cur.dentry->d_name.name, comp, clen))
+ ret = -EINVAL;
+ spin_unlock(&cur.dentry->d_lock);
+ if (ret)
+ break;
+
+ parent = dget_parent(cur.dentry);
+ if (parent == cur.dentry) {
+ /* escaped the mount without reaching the share root */
+ dput(parent);
+ ret = -EINVAL;
+ break;
+ }
+ dput(cur.dentry);
+ cur.dentry = parent;
+ end = comp;
+ if (end > p && end[-1] == '/')
+ end--;
+ }
+ path_put(&cur);
+ if (!read_seqretry(&rename_lock, seq))
+ break;
+ }
+ if (ret)
+ ksmbd_debug(SMB, "invalid name reconnect %s\n", name);
return ret;
}