Commit 228a1157 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag '6.13-rc-part1-SMB3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6

Pull smb client updates from Steve French:

 - Fix two SMB3.1.1 POSIX Extensions problems

 - Fixes for special file handling (symlinks and FIFOs)

 - Improve compounding

 - Four cleanup patches

 - Fix use after free in signing

 - Add support for handling namespaces for reconnect related upcalls
   (e.g. for DNS names resolution and auth)

 - Fix various directory lease problems (directory entry caching),
   including some important potential use after frees

* tag '6.13-rc-part1-SMB3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  smb: prevent use-after-free due to open_cached_dir error paths
  smb: Don't leak cfid when reconnect races with open_cached_dir
  smb: client: handle max length for SMB symlinks
  smb: client: get rid of bounds check in SMB2_ioctl_init()
  smb: client: improve compound padding in encryption
  smb3: request handle caching when caching directories
  cifs: Recognize SFU char/block devices created by Windows NFS server on Windows Server <<2012
  CIFS: New mount option for cifs.upcall namespace resolution
  smb/client: Prevent error pointer dereference
  fs/smb/client: implement chmod() for SMB3 POSIX Extensions
  smb: cached directories can be more than root file handle
  smb: client: fix use-after-free of signing key
  smb: client: Use str_yes_no() helper function
  smb: client: memcpy() with surrounding object base address
  cifs: Remove pre-historic unused CIFSSMBCopy
parents e7675238 a9685b40
Loading
Loading
Loading
Loading
+44 −55
Original line number Diff line number Diff line
@@ -59,6 +59,16 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
	list_add(&cfid->entry, &cfids->entries);
	cfid->on_list = true;
	kref_get(&cfid->refcount);
	/*
	 * Set @cfid->has_lease to true during construction so that the lease
	 * reference can be put in cached_dir_lease_break() due to a potential
	 * lease break right after the request is sent or while @cfid is still
	 * being cached, or if a reconnection is triggered during construction.
	 * Concurrent processes won't be to use it yet due to @cfid->time being
	 * zero.
	 */
	cfid->has_lease = true;

	spin_unlock(&cfids->cfid_list_lock);
	return cfid;
}
@@ -176,12 +186,12 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
		return -ENOENT;
	}
	/*
	 * Return cached fid if it has a lease.  Otherwise, it is either a new
	 * entry or laundromat worker removed it from @cfids->entries.  Caller
	 * will put last reference if the latter.
	 * Return cached fid if it is valid (has a lease and has a time).
	 * Otherwise, it is either a new entry or laundromat worker removed it
	 * from @cfids->entries.  Caller will put last reference if the latter.
	 */
	spin_lock(&cfids->cfid_list_lock);
	if (cfid->has_lease) {
	if (cfid->has_lease && cfid->time) {
		spin_unlock(&cfids->cfid_list_lock);
		*ret_cfid = cfid;
		kfree(utf16_path);
@@ -267,15 +277,6 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,

	smb2_set_related(&rqst[1]);

	/*
	 * Set @cfid->has_lease to true before sending out compounded request so
	 * its lease reference can be put in cached_dir_lease_break() due to a
	 * potential lease break right after the request is sent or while @cfid
	 * is still being cached.  Concurrent processes won't be to use it yet
	 * due to @cfid->time being zero.
	 */
	cfid->has_lease = true;

	if (retries) {
		smb2_set_replay(server, &rqst[0]);
		smb2_set_replay(server, &rqst[1]);
@@ -347,6 +348,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
	SMB2_query_info_free(&rqst[1]);
	free_rsp_buf(resp_buftype[0], rsp_iov[0].iov_base);
	free_rsp_buf(resp_buftype[1], rsp_iov[1].iov_base);
out:
	if (rc) {
		spin_lock(&cfids->cfid_list_lock);
		if (cfid->on_list) {
@@ -358,23 +360,14 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
			/*
			 * We are guaranteed to have two references at this
			 * point. One for the caller and one for a potential
			 * lease. Release the Lease-ref so that the directory
			 * will be closed when the caller closes the cached
			 * handle.
			 * lease. Release one here, and the second below.
			 */
			cfid->has_lease = false;
			spin_unlock(&cfids->cfid_list_lock);
			kref_put(&cfid->refcount, smb2_close_cached_fid);
			goto out;
		}
		spin_unlock(&cfids->cfid_list_lock);
	}
out:
	if (rc) {
		if (cfid->is_open)
			SMB2_close(0, cfid->tcon, cfid->fid.persistent_fid,
				   cfid->fid.volatile_fid);
		free_cached_dir(cfid);

		kref_put(&cfid->refcount, smb2_close_cached_fid);
	} else {
		*ret_cfid = cfid;
		atomic_inc(&tcon->num_remote_opens);
@@ -401,7 +394,7 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
	spin_lock(&cfids->cfid_list_lock);
	list_for_each_entry(cfid, &cfids->entries, entry) {
		if (dentry && cfid->dentry == dentry) {
			cifs_dbg(FYI, "found a cached root file handle by dentry\n");
			cifs_dbg(FYI, "found a cached file handle by dentry\n");
			kref_get(&cfid->refcount);
			*ret_cfid = cfid;
			spin_unlock(&cfids->cfid_list_lock);
@@ -512,7 +505,13 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
		cfids->num_entries--;
		cfid->is_open = false;
		cfid->on_list = false;
		/* To prevent race with smb2_cached_lease_break() */
		if (cfid->has_lease) {
			/*
			 * The lease was never cancelled from the server,
			 * so steal that reference.
			 */
			cfid->has_lease = false;
		} else
			kref_get(&cfid->refcount);
	}
	spin_unlock(&cfids->cfid_list_lock);
@@ -520,17 +519,10 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
	list_for_each_entry_safe(cfid, q, &entry, entry) {
		list_del(&cfid->entry);
		cancel_work_sync(&cfid->lease_break);
		if (cfid->has_lease) {
		/*
			 * We lease was never cancelled from the server so we
			 * need to drop the reference.
		 * Drop the ref-count from above, either the lease-ref (if there
		 * was one) or the extra one acquired.
		 */
			spin_lock(&cfids->cfid_list_lock);
			cfid->has_lease = false;
			spin_unlock(&cfids->cfid_list_lock);
			kref_put(&cfid->refcount, smb2_close_cached_fid);
		}
		/* Drop the extra reference opened above*/
		kref_put(&cfid->refcount, smb2_close_cached_fid);
	}
}
@@ -541,9 +533,6 @@ smb2_cached_lease_break(struct work_struct *work)
	struct cached_fid *cfid = container_of(work,
				struct cached_fid, lease_break);

	spin_lock(&cfid->cfids->cfid_list_lock);
	cfid->has_lease = false;
	spin_unlock(&cfid->cfids->cfid_list_lock);
	kref_put(&cfid->refcount, smb2_close_cached_fid);
}

@@ -561,6 +550,7 @@ int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
		    !memcmp(lease_key,
			    cfid->fid.lease_key,
			    SMB2_LEASE_KEY_SIZE)) {
			cfid->has_lease = false;
			cfid->time = 0;
			/*
			 * We found a lease remove it from the list
@@ -638,7 +628,13 @@ static void cfids_laundromat_worker(struct work_struct *work)
			cfid->on_list = false;
			list_move(&cfid->entry, &entry);
			cfids->num_entries--;
			/* To prevent race with smb2_cached_lease_break() */
			if (cfid->has_lease) {
				/*
				 * Our lease has not yet been cancelled from the
				 * server. Steal that reference.
				 */
				cfid->has_lease = false;
			} else
				kref_get(&cfid->refcount);
		}
	}
@@ -651,17 +647,10 @@ static void cfids_laundromat_worker(struct work_struct *work)
		 * with it.
		 */
		cancel_work_sync(&cfid->lease_break);
		if (cfid->has_lease) {
		/*
			 * Our lease has not yet been cancelled from the server
			 * so we need to drop the reference.
		 * Drop the ref-count from above, either the lease-ref (if there
		 * was one) or the extra one acquired.
		 */
			spin_lock(&cfids->cfid_list_lock);
			cfid->has_lease = false;
			spin_unlock(&cfids->cfid_list_lock);
			kref_put(&cfid->refcount, smb2_close_cached_fid);
		}
		/* Drop the extra reference opened above */
		kref_put(&cfid->refcount, smb2_close_cached_fid);
	}
	queue_delayed_work(cifsiod_wq, &cfids->laundromat_work,
+16 −0
Original line number Diff line number Diff line
@@ -82,6 +82,9 @@ struct key_type cifs_spnego_key_type = {
/* strlen of ";pid=0x" */
#define PID_KEY_LEN		7

/* strlen of ";upcall_target=" */
#define UPCALL_TARGET_KEY_LEN	15

/* get a key struct with a SPNEGO security blob, suitable for session setup */
struct key *
cifs_get_spnego_key(struct cifs_ses *sesInfo,
@@ -108,6 +111,11 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo,
	if (sesInfo->user_name)
		desc_len += USER_KEY_LEN + strlen(sesInfo->user_name);

	if (sesInfo->upcall_target == UPTARGET_MOUNT)
		desc_len += UPCALL_TARGET_KEY_LEN + 5; // strlen("mount")
	else
		desc_len += UPCALL_TARGET_KEY_LEN + 3; // strlen("app")

	spnego_key = ERR_PTR(-ENOMEM);
	description = kzalloc(desc_len, GFP_KERNEL);
	if (description == NULL)
@@ -156,6 +164,14 @@ cifs_get_spnego_key(struct cifs_ses *sesInfo,
	dp = description + strlen(description);
	sprintf(dp, ";pid=0x%x", current->pid);

	if (sesInfo->upcall_target == UPTARGET_MOUNT) {
		dp = description + strlen(description);
		sprintf(dp, ";upcall_target=mount");
	} else {
		dp = description + strlen(description);
		sprintf(dp, ";upcall_target=app");
	}

	cifs_dbg(FYI, "key description = %s\n", description);
	saved_cred = override_creds(spnego_cred);
	spnego_key = request_key(&cifs_spnego_key_type, description, "");
+33 −21
Original line number Diff line number Diff line
@@ -885,11 +885,16 @@ unsigned int setup_authusers_ACE(struct smb_ace *pntace)
 * Fill in the special SID based on the mode. See
 * https://technet.microsoft.com/en-us/library/hh509017(v=ws.10).aspx
 */
unsigned int setup_special_mode_ACE(struct smb_ace *pntace, __u64 nmode)
unsigned int setup_special_mode_ACE(struct smb_ace *pntace,
				    bool posix,
				    __u64 nmode)
{
	int i;
	unsigned int ace_size = 28;

	if (posix)
		pntace->type = ACCESS_ALLOWED_ACE_TYPE;
	else
		pntace->type = ACCESS_DENIED_ACE_TYPE;
	pntace->flags = 0x0;
	pntace->access_req = 0;
@@ -933,7 +938,8 @@ static void populate_new_aces(char *nacl_base,
		struct smb_sid *pownersid,
		struct smb_sid *pgrpsid,
		__u64 *pnmode, u32 *pnum_aces, u16 *pnsize,
		bool modefromsid)
		bool modefromsid,
		bool posix)
{
	__u64 nmode;
	u32 num_aces = 0;
@@ -950,13 +956,15 @@ static void populate_new_aces(char *nacl_base,
	num_aces = *pnum_aces;
	nsize = *pnsize;

	if (modefromsid) {
	if (modefromsid || posix) {
		pnntace = (struct smb_ace *) (nacl_base + nsize);
		nsize += setup_special_mode_ACE(pnntace, nmode);
		nsize += setup_special_mode_ACE(pnntace, posix, nmode);
		num_aces++;
		if (modefromsid) {
			pnntace = (struct smb_ace *) (nacl_base + nsize);
			nsize += setup_authusers_ACE(pnntace);
			num_aces++;
		}
		goto set_size;
	}

@@ -1076,7 +1084,7 @@ static __u16 replace_sids_and_copy_aces(struct smb_acl *pdacl, struct smb_acl *p

static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl,
		struct smb_sid *pownersid,	struct smb_sid *pgrpsid,
		__u64 *pnmode, bool mode_from_sid)
		__u64 *pnmode, bool mode_from_sid, bool posix)
{
	int i;
	u16 size = 0;
@@ -1094,11 +1102,11 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl,
	nsize = sizeof(struct smb_acl);

	/* If pdacl is NULL, we don't have a src. Simply populate new ACL. */
	if (!pdacl) {
	if (!pdacl || posix) {
		populate_new_aces(nacl_base,
				pownersid, pgrpsid,
				pnmode, &num_aces, &nsize,
				mode_from_sid);
				mode_from_sid, posix);
		goto finalize_dacl;
	}

@@ -1115,7 +1123,7 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl,
			populate_new_aces(nacl_base,
					pownersid, pgrpsid,
					pnmode, &num_aces, &nsize,
					mode_from_sid);
					mode_from_sid, posix);

			new_aces_set = true;
		}
@@ -1144,7 +1152,7 @@ static int set_chmod_dacl(struct smb_acl *pdacl, struct smb_acl *pndacl,
		populate_new_aces(nacl_base,
				pownersid, pgrpsid,
				pnmode, &num_aces, &nsize,
				mode_from_sid);
				mode_from_sid, posix);

		new_aces_set = true;
	}
@@ -1251,7 +1259,7 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
/* Convert permission bits from mode to equivalent CIFS ACL */
static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
	__u32 secdesclen, __u32 *pnsecdesclen, __u64 *pnmode, kuid_t uid, kgid_t gid,
	bool mode_from_sid, bool id_from_sid, int *aclflag)
	bool mode_from_sid, bool id_from_sid, bool posix, int *aclflag)
{
	int rc = 0;
	__u32 dacloffset;
@@ -1288,7 +1296,7 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
		ndacl_ptr->num_aces = cpu_to_le32(0);

		rc = set_chmod_dacl(dacl_ptr, ndacl_ptr, owner_sid_ptr, group_sid_ptr,
				    pnmode, mode_from_sid);
				    pnmode, mode_from_sid, posix);

		sidsoffset = ndacloffset + le16_to_cpu(ndacl_ptr->size);
		/* copy the non-dacl portion of secdesc */
@@ -1584,13 +1592,16 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
	struct smb_ntsd *pntsd = NULL; /* acl obtained from server */
	struct smb_ntsd *pnntsd = NULL; /* modified acl to be sent to server */
	struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
	struct tcon_link *tlink = cifs_sb_tlink(cifs_sb);
	struct tcon_link *tlink;
	struct smb_version_operations *ops;
	bool mode_from_sid, id_from_sid;
	const u32 info = 0;
	bool posix;

	tlink = cifs_sb_tlink(cifs_sb);
	if (IS_ERR(tlink))
		return PTR_ERR(tlink);
	posix = tlink_tcon(tlink)->posix_extensions;

	ops = tlink_tcon(tlink)->ses->server->ops;

@@ -1622,12 +1633,13 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
		id_from_sid = false;

	/* Potentially, five new ACEs can be added to the ACL for U,G,O mapping */
	nsecdesclen = secdesclen;
	if (pnmode && *pnmode != NO_CHANGE_64) { /* chmod */
		if (mode_from_sid)
			nsecdesclen += 2 * sizeof(struct smb_ace);
		if (posix)
			nsecdesclen = 1 * sizeof(struct smb_ace);
		else if (mode_from_sid)
			nsecdesclen = secdesclen + (2 * sizeof(struct smb_ace));
		else /* cifsacl */
			nsecdesclen += 5 * sizeof(struct smb_ace);
			nsecdesclen = secdesclen + (5 * sizeof(struct smb_ace));
	} else { /* chown */
		/* When ownership changes, changes new owner sid length could be different */
		nsecdesclen = sizeof(struct smb_ntsd) + (sizeof(struct smb_sid) * 2);
@@ -1657,7 +1669,7 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
	}

	rc = build_sec_desc(pntsd, pnntsd, secdesclen, &nsecdesclen, pnmode, uid, gid,
			    mode_from_sid, id_from_sid, &aclflag);
			    mode_from_sid, id_from_sid, posix, &aclflag);

	cifs_dbg(NOISY, "build_sec_desc rc: %d\n", rc);

+25 −0
Original line number Diff line number Diff line
@@ -546,6 +546,30 @@ static int cifs_show_devname(struct seq_file *m, struct dentry *root)
	return 0;
}

static void
cifs_show_upcall_target(struct seq_file *s, struct cifs_sb_info *cifs_sb)
{
	if (cifs_sb->ctx->upcall_target == UPTARGET_UNSPECIFIED) {
		seq_puts(s, ",upcall_target=app");
		return;
	}

	seq_puts(s, ",upcall_target=");

	switch (cifs_sb->ctx->upcall_target) {
	case UPTARGET_APP:
		seq_puts(s, "app");
		break;
	case UPTARGET_MOUNT:
		seq_puts(s, "mount");
		break;
	default:
		/* shouldn't ever happen */
		seq_puts(s, "unknown");
		break;
	}
}

/*
 * cifs_show_options() is for displaying mount options in /proc/mounts.
 * Not all settable options are displayed but most of the important
@@ -562,6 +586,7 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
	seq_show_option(s, "vers", tcon->ses->server->vals->version_string);
	cifs_show_security(s, tcon->ses);
	cifs_show_cache_flavor(s, cifs_sb);
	cifs_show_upcall_target(s, cifs_sb);

	if (tcon->no_lease)
		seq_puts(s, ",nolease");
+9 −2
Original line number Diff line number Diff line
@@ -153,6 +153,12 @@ enum securityEnum {
	Kerberos,		/* Kerberos via SPNEGO */
};

enum upcall_target_enum {
	UPTARGET_UNSPECIFIED, /* not specified, defaults to app */
	UPTARGET_MOUNT, /* upcall to the mount namespace */
	UPTARGET_APP, /* upcall to the application namespace which did the mount */
};

enum cifs_reparse_type {
	CIFS_REPARSE_TYPE_NFS,
	CIFS_REPARSE_TYPE_WSL,
@@ -1084,6 +1090,7 @@ struct cifs_ses {
	struct session_key auth_key;
	struct ntlmssp_auth *ntlmssp; /* ciphertext, flags, server challenge */
	enum securityEnum sectype; /* what security flavor was specified? */
	enum upcall_target_enum upcall_target; /* what upcall target was specified? */
	bool sign;		/* is signing required? */
	bool domainAuto:1;
	bool expired_pwd;  /* track if access denied or expired pwd so can know if need to update */
@@ -2223,7 +2230,7 @@ static inline int cifs_get_num_sgs(const struct smb_rqst *rqst,
			struct kvec *iov = &rqst[i].rq_iov[j];

			addr = (unsigned long)iov->iov_base + skip;
			if (unlikely(is_vmalloc_addr((void *)addr))) {
			if (is_vmalloc_or_module_addr((void *)addr)) {
				len = iov->iov_len - skip;
				nents += DIV_ROUND_UP(offset_in_page(addr) + len,
						      PAGE_SIZE);
@@ -2250,7 +2257,7 @@ static inline void cifs_sg_set_buf(struct sg_table *sgtable,
	unsigned int off = offset_in_page(addr);

	addr &= PAGE_MASK;
	if (unlikely(is_vmalloc_addr((void *)addr))) {
	if (is_vmalloc_or_module_addr((void *)addr)) {
		do {
			unsigned int len = min_t(unsigned int, buflen, PAGE_SIZE - off);

Loading