Wireshark-dev: [Wireshark-dev] [PATCH 01/16] NFS: Fix NFSv3 READDIRPLUS filename snooping

From: Pali Rohár <pali@xxxxxxxxxx>
Date: Fri, 13 Sep 2024 23:08:36 +0200
---
 epan/dissectors/packet-nfs.c | 109 ++++++++++++++++++++++++++++++-----
 1 file changed, 94 insertions(+), 15 deletions(-)

diff --git a/epan/dissectors/packet-nfs.c b/epan/dissectors/packet-nfs.c
index 4d6ec8503f88..59920f2c4bce 100644
--- a/epan/dissectors/packet-nfs.c
+++ b/epan/dissectors/packet-nfs.c
@@ -1042,6 +1042,7 @@ typedef struct nfs_name_snoop_key {
 	const unsigned char *fh;
 } nfs_name_snoop_key_t;
 
+static GHashTable *nfs_name_snoop_readdir;
 static GHashTable *nfs_name_snoop_unmatched;
 
 static GHashTable *nfs_name_snoop_matched;
@@ -1184,6 +1185,10 @@ nfs_name_snoop_value_destroy(void *value)
 static void
 nfs_name_snoop_init(void)
 {
+	nfs_name_snoop_readdir =
+		g_hash_table_new_full(nfs_name_snoop_unmatched_hash,
+		nfs_name_snoop_unmatched_equal,
+		NULL, nfs_name_snoop_value_destroy);
 	nfs_name_snoop_unmatched =
 		g_hash_table_new_full(nfs_name_snoop_unmatched_hash,
 		nfs_name_snoop_unmatched_equal,
@@ -1197,6 +1202,7 @@ nfs_name_snoop_init(void)
 static void
 nfs_name_snoop_cleanup(void)
 {
+	g_hash_table_destroy(nfs_name_snoop_readdir);
 	g_hash_table_destroy(nfs_name_snoop_unmatched);
 	g_hash_table_destroy(nfs_name_snoop_matched);
 }
@@ -1298,6 +1304,60 @@ nfs_name_snoop_add_fh(int xid, tvbuff_t *tvb, int fh_offset, int fh_length)
 	g_hash_table_replace(nfs_name_snoop_matched, key, nns);
 }
 
+static void
+nfs_name_snoop_readdir_add_parent_fh(int xid, tvbuff_t *tvb, int fh_offset, int fh_length)
+{
+	nfs_name_snoop_t     *nns;
+	unsigned char        *fh;
+
+	fh = (unsigned char *)tvb_memdup(NULL, tvb, fh_offset, fh_length);
+
+	nns = g_new0(nfs_name_snoop_t, 1);
+	nns->parent_len = fh_length;
+	nns->parent = fh;
+
+	g_hash_table_insert(nfs_name_snoop_readdir, GINT_TO_POINTER(xid), nns);
+}
+
+static void
+nfs_name_snoop_readdir_add_child_fh_name(int xid, tvbuff_t *tvb, int fh_offset, int fh_length, const char *name)
+{
+	nfs_name_snoop_t     *readdir_nns;
+	unsigned char        *fh;
+	nfs_name_snoop_t     *nns;
+	nfs_name_snoop_key_t *key;
+
+	if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+		return;
+
+	readdir_nns = (nfs_name_snoop_t *)g_hash_table_lookup(nfs_name_snoop_readdir, GINT_TO_POINTER(xid));
+	if (!readdir_nns)
+		return;
+
+	fh = (unsigned char *)tvb_memdup(NULL, tvb, fh_offset, fh_length);
+
+	nns = g_new(nfs_name_snoop_t, 1);
+	nns->fh_length = fh_length;
+	nns->fh = fh;
+	nns->parent_len = readdir_nns->parent_len;
+	nns->parent = g_malloc(readdir_nns->parent_len);
+	memcpy(nns->parent, readdir_nns->parent, readdir_nns->parent_len);
+	nns->name_len = (int)strlen(name);
+	nns->name = g_strdup(name);
+	nns->full_name_len = 0;
+	nns->full_name = NULL;
+	nns->fs_cycle = false;
+
+	key = wmem_new(wmem_file_scope(), nfs_name_snoop_key_t);
+	key->key = 0;
+	key->fh_length = nns->fh_length;
+	key->fh = nns->fh;
+
+	g_hash_table_replace(nfs_name_snoop_matched, key, nns);
+
+	/* do not remove entry from the nfs_name_snoop_readdir as it is used for more child names, not just this one */
+}
+
 #define NFS_MAX_FS_DEPTH 100
 
 static void
@@ -3794,12 +3854,12 @@ dissect_nfs3_fh(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree,
 
 	/* are we snooping fh to filenames ?*/
 	if ((!pinfo->fd->visited) && nfs_file_name_snooping) {
-		/* NFS v3 LOOKUP, CREATE, MKDIR, READDIRPLUS
+		/* NFS v3 LOOKUP, CREATE, MKDIR
 			calls might give us a mapping*/
 		if ( ((civ->prog == 100003)
 		  &&((civ->vers == 3)
 		  &&(!civ->request)
-		  &&((civ->proc == 3)||(civ->proc == 8)||(civ->proc == 9)||(civ->proc == 17))))
+		  &&((civ->proc == 3)||(civ->proc == 8)||(civ->proc == 9))))
 		|| civ->vers == 4
 		) {
 			fh_length = tvb_get_ntohl(tvb, offset);
@@ -3808,6 +3868,17 @@ dissect_nfs3_fh(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree,
 					      fh_length);
 		}
 
+		/* NFS v3 READDIRPLUS calls might give us a mapping */
+		if ( (civ->prog == 100003)
+		  &&(civ->vers == 3)
+		  &&(civ->request)
+		  &&((civ->proc == 17))
+		) {
+			fh_length = tvb_get_ntohl(tvb, offset);
+			fh_offset = offset+4;
+			nfs_name_snoop_readdir_add_parent_fh(civ->xid, tvb, fh_offset, fh_length);
+		}
+
 		/* MOUNT v3 MNT replies might give us a filehandle */
 		if ( (civ->prog == 100005)
 		  &&(civ->vers == 3)
@@ -5654,6 +5725,10 @@ dissect_nfs3_entryplus(tvbuff_t *tvb, int offset, packet_info *pinfo,
 {
 	proto_item *entry_item;
 	proto_tree *entry_tree;
+	int fh_follows_offset;
+	bool fh_follows;
+	int fh_offset;
+	int fh_length;
 	int	    old_offset = offset;
 	const char *name       = NULL;
 	rpc_call_info_value *civ = (rpc_call_info_value *)data;
@@ -5665,29 +5740,33 @@ dissect_nfs3_entryplus(tvbuff_t *tvb, int offset, packet_info *pinfo,
 
 	offset = dissect_nfs3_filename(tvb, offset, entry_tree,	hf_nfs3_readdirplus_entry_name, &name);
 
+	proto_item_set_text(entry_item, "Entry: name %s", name);
+
+	col_append_fstr(pinfo->cinfo, COL_INFO, " %s", name);
+
+	offset = dissect_rpc_uint64(tvb, entry_tree, hf_nfs3_readdirplus_entry_cookie,
+		offset);
+
+	offset = dissect_nfs3_post_op_attr(tvb, offset, pinfo, entry_tree, "name_attributes");
+
 	/* are we snooping fh to filenames ?*/
 	if ((!pinfo->fd->visited) && nfs_file_name_snooping) {
-		/* v3 READDIRPLUS replies will give us a mapping */
+		/* v3 READDIRPLUS replies might give us a mapping */
 		if ( (civ->prog == 100003)
 		  &&(civ->vers == 3)
 		  &&(!civ->request)
 		  &&((civ->proc == 17))
 		) {
-			nfs_name_snoop_add_name(civ->xid, tvb, 0, 0,
-				0/*parent offset*/, 0/*parent len*/,
-				name);
+			fh_follows_offset = offset;
+			fh_follows = tvb_get_ntohl(tvb, fh_follows_offset);
+			if (fh_follows == true) {
+				fh_length = tvb_get_ntohl(tvb, fh_follows_offset+4);
+				fh_offset = fh_follows_offset+4+4;
+				nfs_name_snoop_readdir_add_child_fh_name(civ->xid, tvb, fh_offset, fh_length, name);
+			}
 		}
 	}
 
-	proto_item_set_text(entry_item, "Entry: name %s", name);
-
-	col_append_fstr(pinfo->cinfo, COL_INFO, " %s", name);
-
-	offset = dissect_rpc_uint64(tvb, entry_tree, hf_nfs3_readdirplus_entry_cookie,
-		offset);
-
-	offset = dissect_nfs3_post_op_attr(tvb, offset, pinfo, entry_tree, "name_attributes");
-
 	offset = dissect_nfs3_post_op_fh(tvb, offset, pinfo, entry_tree, "name_handle", civ);
 
 	/* now we know, that a readdirplus entry is shorter */
-- 
2.20.1