From 4a8f6c964d5ca86df27c50e50dc1b60d39c9b76d Mon Sep 17 00:00:00 2001
From: Ralph Boehme <slow@samba.org>
Date: Thu, 10 Mar 2022 13:24:28 +0100
Subject: [PATCH] CVE-2022-23123: libatalk: harden ad_entry()

Also fixes:
- CVE-2022-23122
- CVE-2022-23124
- CVE-2022-0194

Signed-off-by: Ralph Boehme <slow@samba.org>
---
 include/atalk/adouble.h    |   3 +-
 libatalk/adouble/ad_open.c | 117 +++++++++++++++++++++++++++++++++++++
 2 files changed, 119 insertions(+), 1 deletion(-)

Index: netatalk-3.1.0/include/atalk/adouble.h
===================================================================
--- netatalk-3.1.0.orig/include/atalk/adouble.h	2013-10-28 16:46:51.000000000 +0100
+++ netatalk-3.1.0/include/atalk/adouble.h	2022-03-22 13:40:03.279889725 +0100
@@ -110,6 +110,8 @@
 #define ADEDLEN_MACFILEI        4
 #define ADEDLEN_PRODOSFILEI     8
 #define ADEDLEN_MSDOSFILEI      2
+#define ADEDLEN_ICONBW          128
+#define ADEDLEN_ICONCOL         1024
 #define ADEDLEN_DID             4
 #define ADEDLEN_PRIVDEV         8
 #define ADEDLEN_PRIVINO         8
@@ -222,6 +224,7 @@ struct adouble {
     char                *ad_name;          /* mac name (maccharset or UTF8-MAC)       */
     struct adouble_fops *ad_ops;
     uint16_t            ad_open_forks;     /* open forks (by others)                  */
+    size_t              valid_data_len;	   /* Bytes read into ad_data                 */
     char                ad_data[AD_DATASZ_MAX];
 };
 
@@ -362,7 +365,6 @@ struct adouble {
 #define ad_getentrylen(ad,eid)     ((ad)->ad_eid[(eid)].ade_len)
 #define ad_setentrylen(ad,eid,len) ((ad)->ad_eid[(eid)].ade_len = (len))
 #define ad_setentryoff(ad,eid,off) ((ad)->ad_eid[(eid)].ade_off = (off))
-#define ad_entry(ad,eid)           ((caddr_t)(ad)->ad_data + (ad)->ad_eid[(eid)].ade_off)
 
 #define ad_get_RF_flags(ad) ((ad)->ad_rfp->adf_flags)
 #define ad_get_MD_flags(ad) ((ad)->ad_mdp->adf_flags)
@@ -389,6 +391,7 @@ extern void ad_unlock(struct adouble *,
 extern int ad_tmplock(struct adouble *, uint32_t eid, int type, off_t off, off_t len, int fork);
 
 /* ad_open.c */
+extern void *ad_entry(const struct adouble *ad, int eid);
 extern off_t ad_getentryoff(const struct adouble *ad, int eid);
 extern const char *adflags2logstr(int adflags);
 extern int ad_setfuid     (const uid_t );
Index: netatalk-3.1.0/libatalk/adouble/ad_open.c
===================================================================
--- netatalk-3.1.0.orig/libatalk/adouble/ad_open.c	2022-03-22 13:40:03.271889679 +0100
+++ netatalk-3.1.0/libatalk/adouble/ad_open.c	2022-03-22 13:40:03.279889725 +0100
@@ -420,6 +420,7 @@ static int parse_entries(struct adouble
         ad->ad_eid[eid].ade_len = len;
     }
 
+    ad->valid_data_len = valid_data_len;
     return 0;
 }
 
@@ -1417,6 +1418,122 @@ static int ad_open_rf(const char *path,
  * API functions
  ********************************************************************************* */
 
+/*
+ * All entries besides FinderInfo and resource fork must fit into the
+ * buffer. FinderInfo is special as it may be larger then the default 32 bytes
+ * if it contains marshalled xattrs, which we will fixup that in
+ * ad_convert(). The first 32 bytes however must also be part of the buffer.
+ *
+ * The resource fork is never accessed directly by the ad_data buf.
+ */
+static bool ad_entry_check_size(uint32_t eid,
+				size_t bufsize,
+				uint32_t off,
+				uint32_t got_len)
+{
+	struct {
+		off_t expected_len;
+		bool fixed_size;
+		bool minimum_size;
+	} ad_checks[] = {
+		[ADEID_DFORK] = {-1, false, false}, /* not applicable */
+		[ADEID_RFORK] = {-1, false, false}, /* no limit */
+		[ADEID_NAME] = {ADEDLEN_NAME, false, false},
+		[ADEID_COMMENT] = {ADEDLEN_COMMENT, false, false},
+		[ADEID_ICONBW] = {ADEDLEN_ICONBW, true, false},
+		[ADEID_ICONCOL] = {ADEDLEN_ICONCOL, false, false},
+		[ADEID_FILEI] = {ADEDLEN_FILEI, true, false},
+		[ADEID_FILEDATESI] = {ADEDLEN_FILEDATESI, true, false},
+		[ADEID_FINDERI] = {ADEDLEN_FINDERI, false, true},
+		[ADEID_MACFILEI] = {ADEDLEN_MACFILEI, true, false},
+		[ADEID_PRODOSFILEI] = {ADEDLEN_PRODOSFILEI, true, false},
+		[ADEID_MSDOSFILEI] = {ADEDLEN_MSDOSFILEI, true, false},
+		[ADEID_SHORTNAME] = {ADEDLEN_SHORTNAME, false, false},
+		[ADEID_AFPFILEI] = {ADEDLEN_AFPFILEI, true, false},
+		[ADEID_DID] = {ADEDLEN_DID, true, false},
+		[ADEID_PRIVDEV] = {ADEDLEN_PRIVDEV, true, false},
+		[ADEID_PRIVINO] = {ADEDLEN_PRIVINO, true, false},
+		[ADEID_PRIVSYN] = {ADEDLEN_PRIVSYN, true, false},
+		[ADEID_PRIVID] = {ADEDLEN_PRIVID, true, false},
+	};
+    uint32_t required_len;
+
+	if (eid >= ADEID_MAX) {
+		return false;
+	}
+	if (got_len == 0) {
+		/* Entry present, but empty, allow */
+		return true;
+	}
+	if (ad_checks[eid].expected_len == 0) {
+		/*
+		 * Shouldn't happen: implicitly initialized to zero because
+		 * explicit initializer missing.
+		 */
+		return false;
+	}
+	if (ad_checks[eid].expected_len == -1) {
+		/* Unused or no limit */
+		return true;
+	}
+	if (ad_checks[eid].fixed_size) {
+		if (ad_checks[eid].expected_len != got_len) {
+			/* Wrong size fo fixed size entry. */
+			return false;
+		}
+        required_len = got_len;
+	} else {
+		if (ad_checks[eid].minimum_size) {
+			if (got_len < ad_checks[eid].expected_len) {
+				/*
+				 * Too small for variable sized entry with
+				 * minimum size.
+				 */
+				return false;
+			}
+        required_len = got_len;
+		} else {
+			if (got_len > ad_checks[eid].expected_len) {
+				/* Too big for variable sized entry. */
+				return false;
+			}
+            /*
+             * Expect the buffer to provide enough room for the maximum possible
+             * size.
+             */
+            required_len = ad_checks[eid].expected_len;
+		}
+	}
+	if (off + required_len < off) {
+		/* wrap around */
+		return false;
+	}
+	if (off + required_len > bufsize) {
+		/* overflow */
+		return false;
+	}
+	return true;
+}
+
+void *ad_entry(const struct adouble *ad, int eid)
+{
+	size_t bufsize = ad->valid_data_len;
+	off_t off = ad_getentryoff(ad, eid);
+	size_t len = ad_getentrylen(ad, eid);
+	bool valid;
+
+	valid = ad_entry_check_size(eid, bufsize, off, len);
+	if (!valid) {
+		return NULL;
+	}
+
+	if (off == 0 || len == 0) {
+		return NULL;
+	}
+
+	return ((struct adouble *)ad)->ad_data + off;
+}
+
 off_t ad_getentryoff(const struct adouble *ad, int eid)
 {
     if (ad->ad_vers == AD_VERSION2)
