Commit 5e5d7597 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag '5.15-rc2-ksmbd-fixes' of git://git.samba.org/ksmbd

Pull ksmbd fixes from Steve French:
 "Five fixes for the ksmbd kernel server, including three security
  fixes:

   - remove follow symlinks support

   - use LOOKUP_BENEATH to prevent out of share access

   - SMB3 compounding security fix

   - fix for returning the default streams correctly, fixing a bug when
     writing ppt or doc files from some clients

   - logging more clearly that ksmbd is experimental (at module load
     time)"

* tag '5.15-rc2-ksmbd-fixes' of git://git.samba.org/ksmbd:
  ksmbd: use LOOKUP_BENEATH to prevent the out of share access
  ksmbd: remove follow symlinks support
  ksmbd: check protocol id in ksmbd_verify_smb_message()
  ksmbd: add default data stream name in FILE_STREAM_INFORMATION
  ksmbd: log that server is experimental at module load
parents 996148ee 265fd199
Loading
Loading
Loading
Loading
+19 −81
Original line number Diff line number Diff line
@@ -158,25 +158,21 @@ int parse_stream_name(char *filename, char **stream_name, int *s_type)
 * Return : windows path string or error
 */

char *convert_to_nt_pathname(char *filename, char *sharepath)
char *convert_to_nt_pathname(char *filename)
{
	char *ab_pathname;
	int len, name_len;

	name_len = strlen(filename);
	ab_pathname = kmalloc(name_len, GFP_KERNEL);
	if (!ab_pathname)
		return NULL;

	if (strlen(filename) == 0) {
		ab_pathname = kmalloc(2, GFP_KERNEL);
		ab_pathname[0] = '\\';
		ab_pathname[1] = '\0';
	} else {
		ab_pathname = kstrdup(filename, GFP_KERNEL);
		if (!ab_pathname)
			return NULL;

	len = strlen(sharepath);
	if (!strncmp(filename, sharepath, len) && name_len != len) {
		strscpy(ab_pathname, &filename[len], name_len);
		ksmbd_conv_path_to_windows(ab_pathname);
	}

	return ab_pathname;
}

@@ -191,77 +187,19 @@ int get_nlink(struct kstat *st)
	return nlink;
}

char *ksmbd_conv_path_to_unix(char *path)
void ksmbd_conv_path_to_unix(char *path)
{
	size_t path_len, remain_path_len, out_path_len;
	char *out_path, *out_next;
	int i, pre_dotdot_cnt = 0, slash_cnt = 0;
	bool is_last;

	strreplace(path, '\\', '/');
	path_len = strlen(path);
	remain_path_len = path_len;
	if (path_len == 0)
		return ERR_PTR(-EINVAL);

	out_path = kzalloc(path_len + 2, GFP_KERNEL);
	if (!out_path)
		return ERR_PTR(-ENOMEM);
	out_path_len = 0;
	out_next = out_path;

	do {
		char *name = path + path_len - remain_path_len;
		char *next = strchrnul(name, '/');
		size_t name_len = next - name;

		is_last = !next[0];
		if (name_len == 2 && name[0] == '.' && name[1] == '.') {
			pre_dotdot_cnt++;
			/* handle the case that path ends with "/.." */
			if (is_last)
				goto follow_dotdot;
		} else {
			if (pre_dotdot_cnt) {
follow_dotdot:
				slash_cnt = 0;
				for (i = out_path_len - 1; i >= 0; i--) {
					if (out_path[i] == '/' &&
					    ++slash_cnt == pre_dotdot_cnt + 1)
						break;
				}

				if (i < 0 &&
				    slash_cnt != pre_dotdot_cnt) {
					kfree(out_path);
					return ERR_PTR(-EINVAL);
}

				out_next = &out_path[i+1];
				*out_next = '\0';
				out_path_len = i + 1;

			}
void ksmbd_strip_last_slash(char *path)
{
	int len = strlen(path);

			if (name_len != 0 &&
			    !(name_len == 1 && name[0] == '.') &&
			    !(name_len == 2 && name[0] == '.' && name[1] == '.')) {
				next[0] = '\0';
				sprintf(out_next, "%s/", name);
				out_next += name_len + 1;
				out_path_len += name_len + 1;
				next[0] = '/';
			}
			pre_dotdot_cnt = 0;
	while (len && path[len - 1] == '/') {
		path[len - 1] = '\0';
		len--;
	}

		remain_path_len -= name_len + 1;
	} while (!is_last);

	if (out_path_len > 0)
		out_path[out_path_len-1] = '\0';
	path[path_len] = '\0';
	return out_path;
}

void ksmbd_conv_path_to_windows(char *path)
@@ -298,7 +236,7 @@ char *ksmbd_extract_sharename(char *treename)
 *
 * Return:	converted name on success, otherwise NULL
 */
char *convert_to_unix_name(struct ksmbd_share_config *share, char *name)
char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name)
{
	int no_slash = 0, name_len, path_len;
	char *new_name;
+4 −3
Original line number Diff line number Diff line
@@ -14,12 +14,13 @@ struct ksmbd_file;
int match_pattern(const char *str, size_t len, const char *pattern);
int ksmbd_validate_filename(char *filename);
int parse_stream_name(char *filename, char **stream_name, int *s_type);
char *convert_to_nt_pathname(char *filename, char *sharepath);
char *convert_to_nt_pathname(char *filename);
int get_nlink(struct kstat *st);
char *ksmbd_conv_path_to_unix(char *path);
void ksmbd_conv_path_to_unix(char *path);
void ksmbd_strip_last_slash(char *path);
void ksmbd_conv_path_to_windows(char *path);
char *ksmbd_extract_sharename(char *treename);
char *convert_to_unix_name(struct ksmbd_share_config *share, char *name);
char *convert_to_unix_name(struct ksmbd_share_config *share, const char *name);

#define KSMBD_DIR_INFO_ALIGNMENT	8
struct ksmbd_dir_info;
+3 −0
Original line number Diff line number Diff line
@@ -584,6 +584,9 @@ static int __init ksmbd_server_init(void)
	ret = ksmbd_workqueue_init();
	if (ret)
		goto err_crypto_destroy;

	pr_warn_once("The ksmbd server is experimental, use at your own risk.\n");

	return 0;

err_crypto_destroy:
+38 −81
Original line number Diff line number Diff line
@@ -433,7 +433,7 @@ static void init_chained_smb2_rsp(struct ksmbd_work *work)
		work->compound_pfid = KSMBD_NO_FID;
	}
	memset((char *)rsp_hdr + 4, 0, sizeof(struct smb2_hdr) + 2);
	rsp_hdr->ProtocolId = rcv_hdr->ProtocolId;
	rsp_hdr->ProtocolId = SMB2_PROTO_NUMBER;
	rsp_hdr->StructureSize = SMB2_HEADER_STRUCTURE_SIZE;
	rsp_hdr->Command = rcv_hdr->Command;

@@ -634,7 +634,7 @@ static char *
smb2_get_name(struct ksmbd_share_config *share, const char *src,
	      const int maxlen, struct nls_table *local_nls)
{
	char *name, *norm_name, *unixname;
	char *name;

	name = smb_strndup_from_utf16(src, maxlen, 1, local_nls);
	if (IS_ERR(name)) {
@@ -642,23 +642,9 @@ smb2_get_name(struct ksmbd_share_config *share, const char *src,
		return name;
	}

	/* change it to absolute unix name */
	norm_name = ksmbd_conv_path_to_unix(name);
	if (IS_ERR(norm_name)) {
		kfree(name);
		return norm_name;
	}
	kfree(name);

	unixname = convert_to_unix_name(share, norm_name);
	kfree(norm_name);
	if (!unixname) {
		pr_err("can not convert absolute name\n");
		return ERR_PTR(-ENOMEM);
	}

	ksmbd_debug(SMB, "absolute name = %s\n", unixname);
	return unixname;
	ksmbd_conv_path_to_unix(name);
	ksmbd_strip_last_slash(name);
	return name;
}

int setup_async_work(struct ksmbd_work *work, void (*fn)(void **), void **arg)
@@ -2352,7 +2338,7 @@ static int smb2_creat(struct ksmbd_work *work, struct path *path, char *name,
			return rc;
	}

	rc = ksmbd_vfs_kern_path(name, 0, path, 0);
	rc = ksmbd_vfs_kern_path(work, name, 0, path, 0);
	if (rc) {
		pr_err("cannot get linux path (%s), err = %d\n",
		       name, rc);
@@ -2427,7 +2413,7 @@ int smb2_open(struct ksmbd_work *work)
	struct oplock_info *opinfo;
	__le32 *next_ptr = NULL;
	int req_op_level = 0, open_flags = 0, may_flags = 0, file_info = 0;
	int rc = 0, len = 0;
	int rc = 0;
	int contxt_cnt = 0, query_disk_id = 0;
	int maximal_access_ctxt = 0, posix_ctxt = 0;
	int s_type = 0;
@@ -2499,17 +2485,11 @@ int smb2_open(struct ksmbd_work *work)
			goto err_out1;
		}
	} else {
		len = strlen(share->path);
		ksmbd_debug(SMB, "share path len %d\n", len);
		name = kmalloc(len + 1, GFP_KERNEL);
		name = kstrdup("", GFP_KERNEL);
		if (!name) {
			rsp->hdr.Status = STATUS_NO_MEMORY;
			rc = -ENOMEM;
			goto err_out1;
		}

		memcpy(name, share->path, len);
		*(name + len) = '\0';
	}

	req_op_level = req->RequestedOplockLevel;
@@ -2632,13 +2612,9 @@ int smb2_open(struct ksmbd_work *work)
		goto err_out1;
	}

	if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
		/*
		 * On delete request, instead of following up, need to
		 * look the current entity
		 */
		rc = ksmbd_vfs_kern_path(name, 0, &path, 1);
	rc = ksmbd_vfs_kern_path(work, name, LOOKUP_NO_SYMLINKS, &path, 1);
	if (!rc) {
		if (req->CreateOptions & FILE_DELETE_ON_CLOSE_LE) {
			/*
			 * If file exists with under flags, return access
			 * denied error.
@@ -2657,34 +2633,16 @@ int smb2_open(struct ksmbd_work *work)
				path_put(&path);
				goto err_out;
			}
		}
	} else {
		if (test_share_config_flag(work->tcon->share_conf,
					   KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS)) {
			/*
			 * Use LOOKUP_FOLLOW to follow the path of
			 * symlink in path buildup
			 */
			rc = ksmbd_vfs_kern_path(name, LOOKUP_FOLLOW, &path, 1);
			if (rc) { /* Case for broken link ?*/
				rc = ksmbd_vfs_kern_path(name, 0, &path, 1);
			}
		} else {
			rc = ksmbd_vfs_kern_path(name, 0, &path, 1);
			if (!rc && d_is_symlink(path.dentry)) {
		} else if (d_is_symlink(path.dentry)) {
			rc = -EACCES;
			path_put(&path);
			goto err_out;
		}
	}
	}

	if (rc) {
		if (rc == -EACCES) {
			ksmbd_debug(SMB,
				    "User does not have right permission\n");
		if (rc != -ENOENT)
			goto err_out;
		}
		ksmbd_debug(SMB, "can not get linux path for %s, rc = %d\n",
			    name, rc);
		rc = 0;
@@ -3180,7 +3138,7 @@ int smb2_open(struct ksmbd_work *work)
			rsp->hdr.Status = STATUS_INVALID_PARAMETER;
		else if (rc == -EOPNOTSUPP)
			rsp->hdr.Status = STATUS_NOT_SUPPORTED;
		else if (rc == -EACCES || rc == -ESTALE)
		else if (rc == -EACCES || rc == -ESTALE || rc == -EXDEV)
			rsp->hdr.Status = STATUS_ACCESS_DENIED;
		else if (rc == -ENOENT)
			rsp->hdr.Status = STATUS_OBJECT_NAME_INVALID;
@@ -4296,8 +4254,7 @@ static int get_file_all_info(struct ksmbd_work *work,
		return -EACCES;
	}

	filename = convert_to_nt_pathname(fp->filename,
					  work->tcon->share_conf->path);
	filename = convert_to_nt_pathname(fp->filename);
	if (!filename)
		return -ENOMEM;

@@ -4428,17 +4385,15 @@ static void get_file_stream_info(struct ksmbd_work *work,
		file_info->NextEntryOffset = cpu_to_le32(next);
	}

	if (nbytes) {
	if (!S_ISDIR(stat.mode)) {
		file_info = (struct smb2_file_stream_info *)
			&rsp->Buffer[nbytes];
		streamlen = smbConvertToUTF16((__le16 *)file_info->StreamName,
					      "::$DATA", 7, conn->local_nls, 0);
		streamlen *= 2;
		file_info->StreamNameLength = cpu_to_le32(streamlen);
		file_info->StreamSize = S_ISDIR(stat.mode) ? 0 :
			cpu_to_le64(stat.size);
		file_info->StreamAllocationSize = S_ISDIR(stat.mode) ? 0 :
			cpu_to_le64(stat.size);
		file_info->StreamSize = 0;
		file_info->StreamAllocationSize = 0;
		nbytes += sizeof(struct smb2_file_stream_info) + streamlen;
	}

@@ -4753,12 +4708,8 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
	struct path path;
	int rc = 0, len;
	int fs_infoclass_size = 0;
	int lookup_flags = 0;

	if (test_share_config_flag(share, KSMBD_SHARE_FLAG_FOLLOW_SYMLINKS))
		lookup_flags = LOOKUP_FOLLOW;

	rc = ksmbd_vfs_kern_path(share->path, lookup_flags, &path, 0);
	rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path);
	if (rc) {
		pr_err("cannot create vfs path\n");
		return -EIO;
@@ -5307,7 +5258,7 @@ static int smb2_rename(struct ksmbd_work *work,
			goto out;

		len = strlen(new_name);
		if (new_name[len - 1] != '/') {
		if (len > 0 && new_name[len - 1] != '/') {
			pr_err("not allow base filename in rename\n");
			rc = -ESHARE;
			goto out;
@@ -5335,11 +5286,14 @@ static int smb2_rename(struct ksmbd_work *work,
	}

	ksmbd_debug(SMB, "new name %s\n", new_name);
	rc = ksmbd_vfs_kern_path(new_name, 0, &path, 1);
	if (rc)
	rc = ksmbd_vfs_kern_path(work, new_name, LOOKUP_NO_SYMLINKS, &path, 1);
	if (rc) {
		if (rc != -ENOENT)
			goto out;
		file_present = false;
	else
	} else {
		path_put(&path);
	}

	if (ksmbd_share_veto_filename(share, new_name)) {
		rc = -ENOENT;
@@ -5409,11 +5363,14 @@ static int smb2_create_link(struct ksmbd_work *work,
	}

	ksmbd_debug(SMB, "target name is %s\n", target_name);
	rc = ksmbd_vfs_kern_path(link_name, 0, &path, 0);
	if (rc)
	rc = ksmbd_vfs_kern_path(work, link_name, LOOKUP_NO_SYMLINKS, &path, 0);
	if (rc) {
		if (rc != -ENOENT)
			goto out;
		file_present = false;
	else
	} else {
		path_put(&path);
	}

	if (file_info->ReplaceIfExists) {
		if (file_present) {
@@ -5573,7 +5530,7 @@ static int set_file_allocation_info(struct ksmbd_work *work,
		 * inode size is retained by backup inode size.
		 */
		size = i_size_read(inode);
		rc = ksmbd_vfs_truncate(work, NULL, fp, alloc_blks * 512);
		rc = ksmbd_vfs_truncate(work, fp, alloc_blks * 512);
		if (rc) {
			pr_err("truncate failed! filename : %s, err %d\n",
			       fp->filename, rc);
@@ -5610,7 +5567,7 @@ static int set_end_of_file_info(struct ksmbd_work *work, struct ksmbd_file *fp,
	if (inode->i_sb->s_magic != MSDOS_SUPER_MAGIC) {
		ksmbd_debug(SMB, "filename : %s truncated to newsize %lld\n",
			    fp->filename, newsize);
		rc = ksmbd_vfs_truncate(work, NULL, fp, newsize);
		rc = ksmbd_vfs_truncate(work, fp, newsize);
		if (rc) {
			ksmbd_debug(SMB, "truncate failed! filename : %s err %d\n",
				    fp->filename, rc);
@@ -5887,7 +5844,7 @@ int smb2_set_info(struct ksmbd_work *work)
	return 0;

err_out:
	if (rc == -EACCES || rc == -EPERM)
	if (rc == -EACCES || rc == -EPERM || rc == -EXDEV)
		rsp->hdr.Status = STATUS_ACCESS_DENIED;
	else if (rc == -EINVAL)
		rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+9 −4
Original line number Diff line number Diff line
@@ -129,16 +129,22 @@ int ksmbd_lookup_protocol_idx(char *str)
 *
 * check for valid smb signature and packet direction(request/response)
 *
 * Return:      0 on success, otherwise 1
 * Return:      0 on success, otherwise -EINVAL
 */
int ksmbd_verify_smb_message(struct ksmbd_work *work)
{
	struct smb2_hdr *smb2_hdr = work->request_buf;
	struct smb2_hdr *smb2_hdr = work->request_buf + work->next_smb2_rcv_hdr_off;
	struct smb_hdr *hdr;

	if (smb2_hdr->ProtocolId == SMB2_PROTO_NUMBER)
		return ksmbd_smb2_check_message(work);

	hdr = work->request_buf;
	if (*(__le32 *)hdr->Protocol == SMB1_PROTO_NUMBER &&
	    hdr->Command == SMB_COM_NEGOTIATE)
		return 0;

	return -EINVAL;
}

/**
@@ -265,7 +271,6 @@ static int ksmbd_negotiate_smb_dialect(void *buf)
	return BAD_PROT_ID;
}

#define SMB_COM_NEGOTIATE	0x72
int ksmbd_init_smb_server(struct ksmbd_work *work)
{
	struct ksmbd_conn *conn = work->conn;
Loading