diff options
Diffstat (limited to 'fs/smb/client/smb2inode.c')
-rw-r--r-- | fs/smb/client/smb2inode.c | 170 |
1 files changed, 151 insertions, 19 deletions
diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 4b25c660894c..5c02a12251c8 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -85,6 +85,82 @@ static int parse_posix_sids(struct cifs_open_info_data *data, return 0; } +struct wsl_query_ea { + __le32 next; + __u8 name_len; + __u8 name[SMB2_WSL_XATTR_NAME_LEN + 1]; +} __packed; + +#define NEXT_OFF cpu_to_le32(sizeof(struct wsl_query_ea)) + +static const struct wsl_query_ea wsl_query_eas[] = { + { .next = NEXT_OFF, .name_len = SMB2_WSL_XATTR_NAME_LEN, .name = SMB2_WSL_XATTR_UID, }, + { .next = NEXT_OFF, .name_len = SMB2_WSL_XATTR_NAME_LEN, .name = SMB2_WSL_XATTR_GID, }, + { .next = NEXT_OFF, .name_len = SMB2_WSL_XATTR_NAME_LEN, .name = SMB2_WSL_XATTR_MODE, }, + { .next = 0, .name_len = SMB2_WSL_XATTR_NAME_LEN, .name = SMB2_WSL_XATTR_DEV, }, +}; + +static int check_wsl_eas(struct kvec *rsp_iov) +{ + struct smb2_file_full_ea_info *ea; + struct smb2_query_info_rsp *rsp = rsp_iov->iov_base; + unsigned long addr; + u32 outlen, next; + u16 vlen; + u8 nlen; + u8 *end; + + outlen = le32_to_cpu(rsp->OutputBufferLength); + if (outlen < SMB2_WSL_MIN_QUERY_EA_RESP_SIZE || + outlen > SMB2_WSL_MAX_QUERY_EA_RESP_SIZE) + return -EINVAL; + + ea = (void *)((u8 *)rsp_iov->iov_base + + le16_to_cpu(rsp->OutputBufferOffset)); + end = (u8 *)rsp_iov->iov_base + rsp_iov->iov_len; + for (;;) { + if ((u8 *)ea > end - sizeof(*ea)) + return -EINVAL; + + nlen = ea->ea_name_length; + vlen = le16_to_cpu(ea->ea_value_length); + if (nlen != SMB2_WSL_XATTR_NAME_LEN || + (u8 *)ea + nlen + 1 + vlen > end) + return -EINVAL; + + switch (vlen) { + case 4: + if (strncmp(ea->ea_data, SMB2_WSL_XATTR_UID, nlen) && + strncmp(ea->ea_data, SMB2_WSL_XATTR_GID, nlen) && + strncmp(ea->ea_data, SMB2_WSL_XATTR_MODE, nlen)) + return -EINVAL; + break; + case 8: + if (strncmp(ea->ea_data, SMB2_WSL_XATTR_DEV, nlen)) + return -EINVAL; + break; + case 0: + if (!strncmp(ea->ea_data, SMB2_WSL_XATTR_UID, nlen) || + !strncmp(ea->ea_data, SMB2_WSL_XATTR_GID, nlen) || + !strncmp(ea->ea_data, SMB2_WSL_XATTR_MODE, nlen) || + !strncmp(ea->ea_data, SMB2_WSL_XATTR_DEV, nlen)) + break; + fallthrough; + default: + return -EINVAL; + } + + next = le32_to_cpu(ea->next_entry_offset); + if (!next) + break; + if (!IS_ALIGNED(next, 4) || + check_add_overflow((unsigned long)ea, next, &addr)) + return -EINVAL; + ea = (void *)addr; + } + return 0; +} + /* * note: If cfile is passed, the reference to it is dropped here. * So make sure that you do not reuse cfile after return from this func. @@ -119,7 +195,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, __u8 delete_pending[8] = {1, 0, 0, 0, 0, 0, 0, 0}; unsigned int size[2]; void *data[2]; - int len; + unsigned int len; int retries = 0, cur_sleep = 1; replay_again: @@ -476,6 +552,39 @@ replay_again: trace_smb3_get_reparse_compound_enter(xid, ses->Suid, tcon->tid, full_path); break; + case SMB2_OP_QUERY_WSL_EA: + rqst[num_rqst].rq_iov = &vars->ea_iov; + rqst[num_rqst].rq_nvec = 1; + + if (cfile) { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + cfile->fid.persistent_fid, + cfile->fid.volatile_fid, + FILE_FULL_EA_INFORMATION, + SMB2_O_INFO_FILE, 0, + SMB2_WSL_MAX_QUERY_EA_RESP_SIZE, + sizeof(wsl_query_eas), + (void *)wsl_query_eas); + } else { + rc = SMB2_query_info_init(tcon, server, + &rqst[num_rqst], + COMPOUND_FID, + COMPOUND_FID, + FILE_FULL_EA_INFORMATION, + SMB2_O_INFO_FILE, 0, + SMB2_WSL_MAX_QUERY_EA_RESP_SIZE, + sizeof(wsl_query_eas), + (void *)wsl_query_eas); + } + if (!rc && (!cfile || num_rqst > 1)) { + smb2_set_next_command(tcon, &rqst[num_rqst]); + smb2_set_related(&rqst[num_rqst]); + } else if (rc) { + goto finished; + } + num_rqst++; + break; default: cifs_dbg(VFS, "Invalid command\n"); rc = -EINVAL; @@ -665,11 +774,32 @@ finished: memset(iov, 0, sizeof(*iov)); resp_buftype[i + 1] = CIFS_NO_BUFFER; } else { - trace_smb3_set_reparse_compound_err(xid, ses->Suid, + trace_smb3_set_reparse_compound_err(xid, ses->Suid, tcon->tid, rc); } SMB2_ioctl_free(&rqst[num_rqst++]); break; + case SMB2_OP_QUERY_WSL_EA: + if (!rc) { + idata = in_iov[i].iov_base; + qi_rsp = rsp_iov[i + 1].iov_base; + data[0] = (u8 *)qi_rsp + le16_to_cpu(qi_rsp->OutputBufferOffset); + size[0] = le32_to_cpu(qi_rsp->OutputBufferLength); + rc = check_wsl_eas(&rsp_iov[i + 1]); + if (!rc) { + memcpy(idata->wsl.eas, data[0], size[0]); + idata->wsl.eas_len = size[0]; + } + } + if (!rc) { + trace_smb3_query_wsl_ea_compound_done(xid, ses->Suid, + tcon->tid); + } else { + trace_smb3_query_wsl_ea_compound_err(xid, ses->Suid, + tcon->tid, rc); + } + SMB2_query_info_free(&rqst[num_rqst++]); + break; } } SMB2_close_free(&rqst[num_rqst]); @@ -737,11 +867,11 @@ int smb2_query_path_info(const unsigned int xid, struct cifsFileInfo *cfile; struct cached_fid *cfid = NULL; struct smb2_hdr *hdr; - struct kvec in_iov[2], out_iov[3] = {}; + struct kvec in_iov[3], out_iov[3] = {}; int out_buftype[3] = {}; - int cmds[2]; + int cmds[3]; bool islink; - int i, num_cmds; + int i, num_cmds = 0; int rc, rc2; data->adjust_tz = false; @@ -774,21 +904,22 @@ int smb2_query_path_info(const unsigned int xid, close_cached_dir(cfid); return rc; } - cmds[0] = SMB2_OP_QUERY_INFO; + cmds[num_cmds++] = SMB2_OP_QUERY_INFO; } else { - cmds[0] = SMB2_OP_POSIX_QUERY_INFO; + cmds[num_cmds++] = SMB2_OP_POSIX_QUERY_INFO; } in_iov[0].iov_base = data; in_iov[0].iov_len = sizeof(*data); in_iov[1] = in_iov[0]; + in_iov[2] = in_iov[0]; cifs_get_readable_path(tcon, full_path, &cfile); oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, FILE_READ_ATTRIBUTES, FILE_OPEN, create_options, ACL_NO_MODE); rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, - &oparms, in_iov, cmds, 1, cfile, - out_iov, out_buftype, NULL); + &oparms, in_iov, cmds, num_cmds, + cfile, out_iov, out_buftype, NULL); hdr = out_iov[0].iov_base; /* * If first iov is unset, then SMB session was dropped or we've got a @@ -808,17 +939,18 @@ int smb2_query_path_info(const unsigned int xid, if (rc || !data->reparse_point) goto out; - if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK) { - /* symlink already parsed in create response */ - num_cmds = 1; - } else { - cmds[1] = SMB2_OP_GET_REPARSE; - num_cmds = 2; - } + cmds[num_cmds++] = SMB2_OP_QUERY_WSL_EA; + /* + * Skip SMB2_OP_GET_REPARSE if symlink already parsed in create + * response. + */ + if (data->reparse.tag != IO_REPARSE_TAG_SYMLINK) + cmds[num_cmds++] = SMB2_OP_GET_REPARSE; + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, - FILE_READ_ATTRIBUTES, FILE_OPEN, - create_options | OPEN_REPARSE_POINT, - ACL_NO_MODE); + FILE_READ_ATTRIBUTES | FILE_READ_EA, + FILE_OPEN, create_options | + OPEN_REPARSE_POINT, ACL_NO_MODE); cifs_get_readable_path(tcon, full_path, &cfile); rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, in_iov, cmds, num_cmds, |