cifs: check for size before setting/adding an EA

Message ID 20180515235927.29024-1-lsahlber@redhat.com
State New
Headers show
Series
  • cifs: check for size before setting/adding an EA
Related show

Commit Message

Ronnie Sahlberg May 15, 2018, 11:59 p.m.
RHBZ: 1247871

Before we set/add the xattr to the SMB2 Extended Attributes
we read the existing EAs for the file and check if there is enough
space to store the new attribute. (Assuming it is a new attribute
and not just changing an existing attribute.)

We have a maximum amount of bufferspace we can use when reading the full set
of all EAs. from the server. This limit is currently set at 64kb.
We need to ensure that if we add a new EA that it will fit in the remaining
space of that buffer.  If not and we cause the total size of all EAs to exceed
that maximum we will no longer be able to access the EAs at all.
Even getfattr will then fail with an error due to the response being too big.

Reported-by: Xiaoli Feng <xifeng@redhat.com>
Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
---
 fs/cifs/smb2ops.c   | 44 ++++++++++++++++++++++++++++++++++++++++++--
 fs/cifs/smb2pdu.c   |  5 +++--
 fs/cifs/smb2proto.h |  3 ++-
 3 files changed, 47 insertions(+), 5 deletions(-)

Patch

diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index ceaa358723f0..4f254153fed2 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -615,7 +615,7 @@  smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 
 		rc = SMB2_query_eas(xid, tcon, fid.persistent_fid,
 				    fid.volatile_fid,
-				    ea_buf_size, smb2_data);
+				    ea_buf_size, smb2_data, NULL);
 
 		if (rc != -E2BIG)
 			break;
@@ -648,6 +648,30 @@  smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 }
 
 
+static struct smb2_file_full_ea_info *
+smb2_get_full_ea(const unsigned int xid, struct cifs_tcon *tcon,
+		 struct cifs_fid *fid, u32 *data_len)
+{
+	struct smb2_file_full_ea_info *smb2_data;
+	int rc;
+
+	smb2_data = kzalloc(SMB2_MAX_EA_BUF, GFP_KERNEL);
+	if (smb2_data == NULL)
+		return NULL;
+
+	rc = SMB2_query_eas(xid, tcon, fid->persistent_fid, fid->volatile_fid,
+			    SMB2_MAX_EA_BUF, smb2_data, data_len);
+
+	if (rc) {
+		kfree(smb2_data);
+		return NULL;
+	}
+
+	return smb2_data;
+}
+
+
+
 static int
 smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 	    const char *path, const char *ea_name, const void *ea_value,
@@ -662,6 +686,8 @@  smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 	struct smb2_file_full_ea_info *ea;
 	int ea_name_len = strlen(ea_name);
 	int len;
+	struct smb2_file_full_ea_info *smb2_data;
+	__u32 data_len;
 
 	if (ea_name_len > 255)
 		return -EINVAL;
@@ -671,7 +697,7 @@  smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 		return -ENOMEM;
 
 	oparms.tcon = tcon;
-	oparms.desired_access = FILE_WRITE_EA;
+	oparms.desired_access = FILE_READ_EA | FILE_WRITE_EA;
 	oparms.disposition = FILE_OPEN;
 	oparms.create_options = 0;
 	oparms.fid = &fid;
@@ -684,7 +710,21 @@  smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon,
 		return rc;
 	}
 
+	smb2_data = smb2_get_full_ea(xid, tcon, &fid, &data_len);
+	if (smb2_data == NULL) {
+		SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
+		return -ENOMEM;
+	}
+	kfree(smb2_data);
+	data_len = (data_len + 3) & ~3;
+
 	len = sizeof(ea) + ea_name_len + ea_value_len + 1;
+
+	if (data_len + len > SMB2_MAX_EA_BUF) {
+		SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
+		return -ENOSPC;
+	}
+
 	ea = kzalloc(len, GFP_KERNEL);
 	if (ea == NULL) {
 		SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index 35350057fc23..bd8327e24270 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -2355,14 +2355,15 @@  query_info(const unsigned int xid, struct cifs_tcon *tcon,
 
 int SMB2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 		   u64 persistent_fid, u64 volatile_fid,
-		   int ea_buf_size, struct smb2_file_full_ea_info *data)
+		   int ea_buf_size, struct smb2_file_full_ea_info *data,
+		   u32 *data_len)
 {
 	return query_info(xid, tcon, persistent_fid, volatile_fid,
 			  FILE_FULL_EA_INFORMATION, SMB2_O_INFO_FILE, 0,
 			  ea_buf_size,
 			  sizeof(struct smb2_file_full_ea_info),
 			  (void **)&data,
-			  NULL);
+			  data_len);
 }
 
 int SMB2_query_info(const unsigned int xid, struct cifs_tcon *tcon,
diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
index 4b0db6af7fe7..524ef587bd1c 100644
--- a/fs/cifs/smb2proto.h
+++ b/fs/cifs/smb2proto.h
@@ -138,7 +138,8 @@  extern int SMB2_flush(const unsigned int xid, struct cifs_tcon *tcon,
 extern int SMB2_query_eas(const unsigned int xid, struct cifs_tcon *tcon,
 			  u64 persistent_file_id, u64 volatile_file_id,
 			  int ea_buf_size,
-			  struct smb2_file_full_ea_info *data);
+			  struct smb2_file_full_ea_info *data,
+			  u32 *data_len);
 extern int SMB2_query_info(const unsigned int xid, struct cifs_tcon *tcon,
 			   u64 persistent_file_id, u64 volatile_file_id,
 			   struct smb2_file_all_info *data);