smb: client: handle lack of EA support in smb2_query_path_info()

If the server doesn't support both EAs and reparse point in a file,
the SMB2_QUERY_INFO request will fail with either
STATUS_NO_EAS_ON_FILE or STATUS_EAS_NOT_SUPPORT in the compound chain,
so ignore it as long as reparse point isn't
IO_REPARSE_TAG_LX_(CHR|BLK), which would require the EAs to know about
major/minor numbers.

Reported-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
Paulo Alcantara
2025-01-21 15:25:36 -03:00
committed by Steve French
parent 056e91cbc9
commit 3681c74d34

View File

@@ -176,27 +176,27 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
struct kvec *out_iov, int *out_buftype, struct dentry *dentry)
{
struct reparse_data_buffer *rbuf;
struct smb2_compound_vars *vars = NULL;
struct kvec *rsp_iov, *iov;
struct smb_rqst *rqst;
int rc;
__le16 *utf16_path = NULL;
__u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
struct cifs_fid fid;
struct cifs_ses *ses = tcon->ses;
struct TCP_Server_Info *server;
int num_rqst = 0, i;
int resp_buftype[MAX_COMPOUND];
struct smb2_query_info_rsp *qi_rsp = NULL;
struct smb2_compound_vars *vars = NULL;
__u8 oplock = SMB2_OPLOCK_LEVEL_NONE;
struct cifs_open_info_data *idata;
struct inode *inode = NULL;
int flags = 0;
__u8 delete_pending[8] = {1, 0, 0, 0, 0, 0, 0, 0};
unsigned int size[2];
void *data[2];
unsigned int len;
struct cifs_ses *ses = tcon->ses;
struct reparse_data_buffer *rbuf;
struct TCP_Server_Info *server;
int resp_buftype[MAX_COMPOUND];
int retries = 0, cur_sleep = 1;
__u8 delete_pending[8] = {1,};
struct kvec *rsp_iov, *iov;
struct inode *inode = NULL;
__le16 *utf16_path = NULL;
struct smb_rqst *rqst;
unsigned int size[2];
struct cifs_fid fid;
int num_rqst = 0, i;
unsigned int len;
int tmp_rc, rc;
int flags = 0;
void *data[2];
replay_again:
/* reinitialize for possible replay */
@@ -639,7 +639,14 @@ finished:
tcon->need_reconnect = true;
}
tmp_rc = rc;
for (i = 0; i < num_cmds; i++) {
char *buf = rsp_iov[i + i].iov_base;
if (buf && resp_buftype[i + 1] != CIFS_NO_BUFFER)
rc = server->ops->map_error(buf, false);
else
rc = tmp_rc;
switch (cmds[i]) {
case SMB2_OP_QUERY_INFO:
idata = in_iov[i].iov_base;
@@ -805,6 +812,7 @@ finished:
}
}
SMB2_close_free(&rqst[num_rqst]);
rc = tmp_rc;
num_cmds += 2;
if (out_iov && out_buftype) {
@@ -860,22 +868,52 @@ static int parse_create_response(struct cifs_open_info_data *data,
return rc;
}
/* Check only if SMB2_OP_QUERY_WSL_EA command failed in the compound chain */
static bool ea_unsupported(int *cmds, int num_cmds,
struct kvec *out_iov, int *out_buftype)
{
int i;
if (cmds[num_cmds - 1] != SMB2_OP_QUERY_WSL_EA)
return false;
for (i = 1; i < num_cmds - 1; i++) {
struct smb2_hdr *hdr = out_iov[i].iov_base;
if (out_buftype[i] == CIFS_NO_BUFFER || !hdr ||
hdr->Status != STATUS_SUCCESS)
return false;
}
return true;
}
static inline void free_rsp_iov(struct kvec *iovs, int *buftype, int count)
{
int i;
for (i = 0; i < count; i++) {
free_rsp_buf(buftype[i], iovs[i].iov_base);
memset(&iovs[i], 0, sizeof(*iovs));
buftype[i] = CIFS_NO_BUFFER;
}
}
int smb2_query_path_info(const unsigned int xid,
struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb,
const char *full_path,
struct cifs_open_info_data *data)
{
struct cifs_open_parms oparms;
__u32 create_options = 0;
struct cifsFileInfo *cfile;
struct kvec in_iov[3], out_iov[5] = {};
struct cached_fid *cfid = NULL;
struct cifs_open_parms oparms;
struct cifsFileInfo *cfile;
__u32 create_options = 0;
int out_buftype[5] = {};
struct smb2_hdr *hdr;
struct kvec in_iov[3], out_iov[3] = {};
int out_buftype[3] = {};
int num_cmds = 0;
int cmds[3];
bool islink;
int i, num_cmds = 0;
int rc, rc2;
data->adjust_tz = false;
@@ -945,14 +983,14 @@ int smb2_query_path_info(const unsigned int xid,
if (rc || !data->reparse_point)
goto out;
if (!tcon->posix_extensions)
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;
if (!tcon->posix_extensions)
cmds[num_cmds++] = SMB2_OP_QUERY_WSL_EA;
oparms = CIFS_OPARMS(cifs_sb, tcon, full_path,
FILE_READ_ATTRIBUTES |
@@ -960,9 +998,18 @@ int smb2_query_path_info(const unsigned int xid,
FILE_OPEN, create_options |
OPEN_REPARSE_POINT, ACL_NO_MODE);
cifs_get_readable_path(tcon, full_path, &cfile);
free_rsp_iov(out_iov, out_buftype, ARRAY_SIZE(out_iov));
rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
&oparms, in_iov, cmds, num_cmds,
cfile, NULL, NULL, NULL);
cfile, out_iov, out_buftype, NULL);
if (rc && ea_unsupported(cmds, num_cmds,
out_iov, out_buftype)) {
if (data->reparse.tag != IO_REPARSE_TAG_LX_BLK &&
data->reparse.tag != IO_REPARSE_TAG_LX_CHR)
rc = 0;
else
rc = -EOPNOTSUPP;
}
break;
case -EREMOTE:
break;
@@ -980,8 +1027,7 @@ int smb2_query_path_info(const unsigned int xid,
}
out:
for (i = 0; i < ARRAY_SIZE(out_buftype); i++)
free_rsp_buf(out_buftype[i], out_iov[i].iov_base);
free_rsp_iov(out_iov, out_buftype, ARRAY_SIZE(out_iov));
return rc;
}