smb: client: introduce SMB2_OP_QUERY_WSL_EA
authorPaulo Alcantara <pc@manguebit.com>
Sun, 28 Jan 2024 04:12:01 +0000 (01:12 -0300)
committerSteve French <stfrench@microsoft.com>
Mon, 11 Mar 2024 00:33:58 +0000 (19:33 -0500)
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>
fs/smb/client/cifsglob.h
fs/smb/client/reparse.c
fs/smb/client/smb2glob.h
fs/smb/client/smb2inode.c
fs/smb/client/smb2pdu.h
fs/smb/client/trace.h

index 395eaed854320e169dcaecffebe5d4a513669cf0..3afae8bf476d881981b8b87557ba5f7df536af25 100644 (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 */
index 24feeaa32280ebb892fae82a8b8da22303aff82a..e8be756e6768c2c3e53a5d33db9b091679f902be 100644 (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));
 
index a0c156996fc51ee301085e12c61fe99a3b2863da..2466e61551369c883db6fdfd95d82cf4e9a0a254 100644 (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. */
index 4b25c660894cf8b119f7f1ce91e080365bff8087..5c02a12251c8486f55095777eedee868282d1a54 100644 (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,
index ea63d33e455322fab49173663c35e8dffa856f81..c72a3b2886b7ff6333b19312198eba060d01507f 100644 (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 */
index 522fa387fcfd72fccc0737b84a4f4cbf3ba72316..ce90ae0d77f84918916083428793aa4ec07650ee 100644 (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);