smb: client: introduce SMB2_OP_QUERY_WSL_EA

Add a new command to smb2_compound_op() for querying WSL extended
attributes from reparse points.

Signed-off-by: Paulo Alcantara <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
Paulo Alcantara 2024-01-28 01:12:01 -03:00 committed by Steve French
parent e0e1e09b2c
commit ea41367b2a
6 changed files with 190 additions and 25 deletions

View File

@ -214,6 +214,10 @@ struct cifs_open_info_data {
struct reparse_posix_data *posix;
};
} reparse;
struct {
__u8 eas[SMB2_WSL_MAX_QUERY_EA_RESP_SIZE];
unsigned int eas_len;
} wsl;
char *symlink_target;
struct cifs_sid posix_owner;
struct cifs_sid posix_group;
@ -2296,6 +2300,7 @@ struct smb2_compound_vars {
struct kvec close_iov;
struct smb2_file_rename_info rename_info;
struct smb2_file_link_info link_info;
struct kvec ea_iov;
};
#endif /* _CIFS_GLOB_H */

View File

@ -205,15 +205,15 @@ static int wsl_set_xattrs(struct inode *inode, umode_t _mode,
__le64 dev = cpu_to_le64(((u64)MINOR(_dev) << 32) | MAJOR(_dev));
__le64 mode = cpu_to_le64(_mode);
struct wsl_xattr xattrs[] = {
{ .name = "$LXUID", .value = uid, .size = 4, },
{ .name = "$LXGID", .value = gid, .size = 4, },
{ .name = "$LXMOD", .value = mode, .size = 4, },
{ .name = "$LXDEV", .value = dev, .size = 8, },
{ .name = SMB2_WSL_XATTR_UID, .value = uid, .size = SMB2_WSL_XATTR_UID_SIZE, },
{ .name = SMB2_WSL_XATTR_GID, .value = gid, .size = SMB2_WSL_XATTR_GID_SIZE, },
{ .name = SMB2_WSL_XATTR_MODE, .value = mode, .size = SMB2_WSL_XATTR_MODE_SIZE, },
{ .name = SMB2_WSL_XATTR_DEV, .value = dev, .size = SMB2_WSL_XATTR_DEV_SIZE, },
};
size_t cc_len;
u32 dlen = 0, next = 0;
int i, num_xattrs;
u8 name_size = strlen(xattrs[0].name) + 1;
u8 name_size = SMB2_WSL_XATTR_NAME_LEN + 1;
memset(iov, 0, sizeof(*iov));

View File

@ -36,7 +36,8 @@ enum smb2_compound_ops {
SMB2_OP_RMDIR,
SMB2_OP_POSIX_QUERY_INFO,
SMB2_OP_SET_REPARSE,
SMB2_OP_GET_REPARSE
SMB2_OP_GET_REPARSE,
SMB2_OP_QUERY_WSL_EA,
};
/* Used when constructing chained read requests. */

View File

@ -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,

View File

@ -420,4 +420,29 @@ struct smb2_create_ea_ctx {
struct smb2_file_full_ea_info ea;
} __packed;
#define SMB2_WSL_XATTR_UID "$LXUID"
#define SMB2_WSL_XATTR_GID "$LXGID"
#define SMB2_WSL_XATTR_MODE "$LXMOD"
#define SMB2_WSL_XATTR_DEV "$LXDEV"
#define SMB2_WSL_XATTR_NAME_LEN 6
#define SMB2_WSL_NUM_XATTRS 4
#define SMB2_WSL_XATTR_UID_SIZE 4
#define SMB2_WSL_XATTR_GID_SIZE 4
#define SMB2_WSL_XATTR_MODE_SIZE 4
#define SMB2_WSL_XATTR_DEV_SIZE 8
#define SMB2_WSL_MIN_QUERY_EA_RESP_SIZE \
(ALIGN((SMB2_WSL_NUM_XATTRS - 1) * \
(SMB2_WSL_XATTR_NAME_LEN + 1 + \
sizeof(struct smb2_file_full_ea_info)), 4) + \
SMB2_WSL_XATTR_NAME_LEN + 1 + sizeof(struct smb2_file_full_ea_info))
#define SMB2_WSL_MAX_QUERY_EA_RESP_SIZE \
(ALIGN(SMB2_WSL_MIN_QUERY_EA_RESP_SIZE + \
SMB2_WSL_XATTR_UID_SIZE + \
SMB2_WSL_XATTR_GID_SIZE + \
SMB2_WSL_XATTR_MODE_SIZE + \
SMB2_WSL_XATTR_DEV_SIZE, 4))
#endif /* _SMB2PDU_H */

View File

@ -411,6 +411,7 @@ DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(set_eof_done);
DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(set_info_compound_done);
DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(set_reparse_compound_done);
DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(get_reparse_compound_done);
DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(query_wsl_ea_compound_done);
DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(delete_done);
DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(mkdir_done);
DEFINE_SMB3_INF_COMPOUND_DONE_EVENT(tdis_done);
@ -456,6 +457,7 @@ DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(set_eof_err);
DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(set_info_compound_err);
DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(set_reparse_compound_err);
DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(get_reparse_compound_err);
DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(query_wsl_ea_compound_err);
DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(mkdir_err);
DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(delete_err);
DEFINE_SMB3_INF_COMPOUND_ERR_EVENT(tdis_err);