cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

cifs_dfs_ref.c (9794B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 *   Contains the CIFS DFS referral mounting routines used for handling
      4 *   traversal via DFS junction point
      5 *
      6 *   Copyright (c) 2007 Igor Mammedov
      7 *   Copyright (C) International Business Machines  Corp., 2008
      8 *   Author(s): Igor Mammedov (niallain@gmail.com)
      9 *		Steve French (sfrench@us.ibm.com)
     10 */
     11
     12#include <linux/dcache.h>
     13#include <linux/mount.h>
     14#include <linux/namei.h>
     15#include <linux/slab.h>
     16#include <linux/vfs.h>
     17#include <linux/fs.h>
     18#include <linux/inet.h>
     19#include "cifsglob.h"
     20#include "cifsproto.h"
     21#include "cifsfs.h"
     22#include "dns_resolve.h"
     23#include "cifs_debug.h"
     24#include "cifs_unicode.h"
     25#include "dfs_cache.h"
     26#include "fs_context.h"
     27
     28static LIST_HEAD(cifs_dfs_automount_list);
     29
     30static void cifs_dfs_expire_automounts(struct work_struct *work);
     31static DECLARE_DELAYED_WORK(cifs_dfs_automount_task,
     32			    cifs_dfs_expire_automounts);
     33static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ;
     34
     35static void cifs_dfs_expire_automounts(struct work_struct *work)
     36{
     37	struct list_head *list = &cifs_dfs_automount_list;
     38
     39	mark_mounts_for_expiry(list);
     40	if (!list_empty(list))
     41		schedule_delayed_work(&cifs_dfs_automount_task,
     42				      cifs_dfs_mountpoint_expiry_timeout);
     43}
     44
     45void cifs_dfs_release_automount_timer(void)
     46{
     47	BUG_ON(!list_empty(&cifs_dfs_automount_list));
     48	cancel_delayed_work_sync(&cifs_dfs_automount_task);
     49}
     50
     51/**
     52 * cifs_build_devname - build a devicename from a UNC and optional prepath
     53 * @nodename:	pointer to UNC string
     54 * @prepath:	pointer to prefixpath (or NULL if there isn't one)
     55 *
     56 * Build a new cifs devicename after chasing a DFS referral. Allocate a buffer
     57 * big enough to hold the final thing. Copy the UNC from the nodename, and
     58 * concatenate the prepath onto the end of it if there is one.
     59 *
     60 * Returns pointer to the built string, or a ERR_PTR. Caller is responsible
     61 * for freeing the returned string.
     62 */
     63static char *
     64cifs_build_devname(char *nodename, const char *prepath)
     65{
     66	size_t pplen;
     67	size_t unclen;
     68	char *dev;
     69	char *pos;
     70
     71	/* skip over any preceding delimiters */
     72	nodename += strspn(nodename, "\\");
     73	if (!*nodename)
     74		return ERR_PTR(-EINVAL);
     75
     76	/* get length of UNC and set pos to last char */
     77	unclen = strlen(nodename);
     78	pos = nodename + unclen - 1;
     79
     80	/* trim off any trailing delimiters */
     81	while (*pos == '\\') {
     82		--pos;
     83		--unclen;
     84	}
     85
     86	/* allocate a buffer:
     87	 * +2 for preceding "//"
     88	 * +1 for delimiter between UNC and prepath
     89	 * +1 for trailing NULL
     90	 */
     91	pplen = prepath ? strlen(prepath) : 0;
     92	dev = kmalloc(2 + unclen + 1 + pplen + 1, GFP_KERNEL);
     93	if (!dev)
     94		return ERR_PTR(-ENOMEM);
     95
     96	pos = dev;
     97	/* add the initial "//" */
     98	*pos = '/';
     99	++pos;
    100	*pos = '/';
    101	++pos;
    102
    103	/* copy in the UNC portion from referral */
    104	memcpy(pos, nodename, unclen);
    105	pos += unclen;
    106
    107	/* copy the prefixpath remainder (if there is one) */
    108	if (pplen) {
    109		*pos = '/';
    110		++pos;
    111		memcpy(pos, prepath, pplen);
    112		pos += pplen;
    113	}
    114
    115	/* NULL terminator */
    116	*pos = '\0';
    117
    118	convert_delimiter(dev, '/');
    119	return dev;
    120}
    121
    122
    123/**
    124 * cifs_compose_mount_options	-	creates mount options for referral
    125 * @sb_mountdata:	parent/root DFS mount options (template)
    126 * @fullpath:		full path in UNC format
    127 * @ref:		optional server's referral
    128 * @devname:		return the built cifs device name if passed pointer not NULL
    129 * creates mount options for submount based on template options sb_mountdata
    130 * and replacing unc,ip,prefixpath options with ones we've got form ref_unc.
    131 *
    132 * Returns: pointer to new mount options or ERR_PTR.
    133 * Caller is responsible for freeing returned value if it is not error.
    134 */
    135char *cifs_compose_mount_options(const char *sb_mountdata,
    136				 const char *fullpath,
    137				 const struct dfs_info3_param *ref,
    138				 char **devname)
    139{
    140	int rc;
    141	char *name;
    142	char *mountdata = NULL;
    143	const char *prepath = NULL;
    144	int md_len;
    145	char *tkn_e;
    146	char *srvIP = NULL;
    147	char sep = ',';
    148	int off, noff;
    149
    150	if (sb_mountdata == NULL)
    151		return ERR_PTR(-EINVAL);
    152
    153	if (ref) {
    154		if (WARN_ON_ONCE(!ref->node_name || ref->path_consumed < 0))
    155			return ERR_PTR(-EINVAL);
    156
    157		if (strlen(fullpath) - ref->path_consumed) {
    158			prepath = fullpath + ref->path_consumed;
    159			/* skip initial delimiter */
    160			if (*prepath == '/' || *prepath == '\\')
    161				prepath++;
    162		}
    163
    164		name = cifs_build_devname(ref->node_name, prepath);
    165		if (IS_ERR(name)) {
    166			rc = PTR_ERR(name);
    167			name = NULL;
    168			goto compose_mount_options_err;
    169		}
    170	} else {
    171		name = cifs_build_devname((char *)fullpath, NULL);
    172		if (IS_ERR(name)) {
    173			rc = PTR_ERR(name);
    174			name = NULL;
    175			goto compose_mount_options_err;
    176		}
    177	}
    178
    179	rc = dns_resolve_server_name_to_ip(name, &srvIP, NULL);
    180	if (rc < 0) {
    181		cifs_dbg(FYI, "%s: Failed to resolve server part of %s to IP: %d\n",
    182			 __func__, name, rc);
    183		goto compose_mount_options_err;
    184	}
    185
    186	/*
    187	 * In most cases, we'll be building a shorter string than the original,
    188	 * but we do have to assume that the address in the ip= option may be
    189	 * much longer than the original. Add the max length of an address
    190	 * string to the length of the original string to allow for worst case.
    191	 */
    192	md_len = strlen(sb_mountdata) + INET6_ADDRSTRLEN;
    193	mountdata = kzalloc(md_len + sizeof("ip=") + 1, GFP_KERNEL);
    194	if (mountdata == NULL) {
    195		rc = -ENOMEM;
    196		goto compose_mount_options_err;
    197	}
    198
    199	/* copy all options except of unc,ip,prefixpath */
    200	off = 0;
    201	if (strncmp(sb_mountdata, "sep=", 4) == 0) {
    202			sep = sb_mountdata[4];
    203			strncpy(mountdata, sb_mountdata, 5);
    204			off += 5;
    205	}
    206
    207	do {
    208		tkn_e = strchr(sb_mountdata + off, sep);
    209		if (tkn_e == NULL)
    210			noff = strlen(sb_mountdata + off);
    211		else
    212			noff = tkn_e - (sb_mountdata + off) + 1;
    213
    214		if (strncasecmp(sb_mountdata + off, "cruid=", 6) == 0) {
    215			off += noff;
    216			continue;
    217		}
    218		if (strncasecmp(sb_mountdata + off, "unc=", 4) == 0) {
    219			off += noff;
    220			continue;
    221		}
    222		if (strncasecmp(sb_mountdata + off, "ip=", 3) == 0) {
    223			off += noff;
    224			continue;
    225		}
    226		if (strncasecmp(sb_mountdata + off, "prefixpath=", 11) == 0) {
    227			off += noff;
    228			continue;
    229		}
    230		strncat(mountdata, sb_mountdata + off, noff);
    231		off += noff;
    232	} while (tkn_e);
    233	strcat(mountdata, sb_mountdata + off);
    234	mountdata[md_len] = '\0';
    235
    236	/* copy new IP and ref share name */
    237	if (mountdata[strlen(mountdata) - 1] != sep)
    238		strncat(mountdata, &sep, 1);
    239	strcat(mountdata, "ip=");
    240	strcat(mountdata, srvIP);
    241
    242	if (devname)
    243		*devname = name;
    244	else
    245		kfree(name);
    246
    247	/*cifs_dbg(FYI, "%s: parent mountdata: %s\n", __func__, sb_mountdata);*/
    248	/*cifs_dbg(FYI, "%s: submount mountdata: %s\n", __func__, mountdata );*/
    249
    250compose_mount_options_out:
    251	kfree(srvIP);
    252	return mountdata;
    253
    254compose_mount_options_err:
    255	kfree(mountdata);
    256	mountdata = ERR_PTR(rc);
    257	kfree(name);
    258	goto compose_mount_options_out;
    259}
    260
    261/**
    262 * cifs_dfs_do_mount - mounts specified path using DFS full path
    263 *
    264 * Always pass down @fullpath to smb3_do_mount() so we can use the root server
    265 * to perform failover in case we failed to connect to the first target in the
    266 * referral.
    267 *
    268 * @mntpt:		directory entry for the path we are trying to automount
    269 * @cifs_sb:		parent/root superblock
    270 * @fullpath:		full path in UNC format
    271 */
    272static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt,
    273					  struct cifs_sb_info *cifs_sb,
    274					  const char *fullpath)
    275{
    276	struct vfsmount *mnt;
    277	char *mountdata;
    278	char *devname;
    279
    280	devname = kstrdup(fullpath, GFP_KERNEL);
    281	if (!devname)
    282		return ERR_PTR(-ENOMEM);
    283
    284	convert_delimiter(devname, '/');
    285
    286	/* TODO: change to call fs_context_for_mount(), fill in context directly, call fc_mount */
    287
    288	/* See afs_mntpt_do_automount in fs/afs/mntpt.c for an example */
    289
    290	/* strip first '\' from fullpath */
    291	mountdata = cifs_compose_mount_options(cifs_sb->ctx->mount_options,
    292					       fullpath + 1, NULL, NULL);
    293	if (IS_ERR(mountdata)) {
    294		kfree(devname);
    295		return (struct vfsmount *)mountdata;
    296	}
    297
    298	mnt = vfs_submount(mntpt, &cifs_fs_type, devname, mountdata);
    299	kfree(mountdata);
    300	kfree(devname);
    301	return mnt;
    302}
    303
    304/*
    305 * Create a vfsmount that we can automount
    306 */
    307static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
    308{
    309	struct cifs_sb_info *cifs_sb;
    310	void *page;
    311	char *full_path;
    312	struct vfsmount *mnt;
    313
    314	cifs_dbg(FYI, "in %s\n", __func__);
    315	BUG_ON(IS_ROOT(mntpt));
    316
    317	/*
    318	 * The MSDFS spec states that paths in DFS referral requests and
    319	 * responses must be prefixed by a single '\' character instead of
    320	 * the double backslashes usually used in the UNC. This function
    321	 * gives us the latter, so we must adjust the result.
    322	 */
    323	cifs_sb = CIFS_SB(mntpt->d_sb);
    324	if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) {
    325		mnt = ERR_PTR(-EREMOTE);
    326		goto cdda_exit;
    327	}
    328
    329	page = alloc_dentry_path();
    330	/* always use tree name prefix */
    331	full_path = build_path_from_dentry_optional_prefix(mntpt, page, true);
    332	if (IS_ERR(full_path)) {
    333		mnt = ERR_CAST(full_path);
    334		goto free_full_path;
    335	}
    336
    337	convert_delimiter(full_path, '\\');
    338	cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path);
    339
    340	mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path);
    341	cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, full_path + 1, mnt);
    342
    343free_full_path:
    344	free_dentry_path(page);
    345cdda_exit:
    346	cifs_dbg(FYI, "leaving %s\n" , __func__);
    347	return mnt;
    348}
    349
    350/*
    351 * Attempt to automount the referral
    352 */
    353struct vfsmount *cifs_dfs_d_automount(struct path *path)
    354{
    355	struct vfsmount *newmnt;
    356
    357	cifs_dbg(FYI, "in %s\n", __func__);
    358
    359	newmnt = cifs_dfs_do_automount(path->dentry);
    360	if (IS_ERR(newmnt)) {
    361		cifs_dbg(FYI, "leaving %s [automount failed]\n" , __func__);
    362		return newmnt;
    363	}
    364
    365	mntget(newmnt); /* prevent immediate expiration */
    366	mnt_set_expiry(newmnt, &cifs_dfs_automount_list);
    367	schedule_delayed_work(&cifs_dfs_automount_task,
    368			      cifs_dfs_mountpoint_expiry_timeout);
    369	cifs_dbg(FYI, "leaving %s [ok]\n" , __func__);
    370	return newmnt;
    371}
    372
    373const struct inode_operations cifs_dfs_referral_inode_operations = {
    374};