From 06ba3b54bba480349cb50cf3562f5b976e3f9937 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(-) diff --git a/include/atalk/adouble.h b/include/atalk/adouble.h index 16dd327d077e..da50b19ca460 100644 --- a/include/atalk/adouble.h +++ b/include/atalk/adouble.h @@ -258,6 +258,7 @@ struct adouble { int ad_m_namelen; struct adouble_fops *ad_ops; u_int16_t ad_open_forks; /* open forks (by others) */ + size_t valid_data_len; /* Bytes read into ad_data */ #ifdef USE_MMAPPED_HEADERS char *ad_data; @@ -416,7 +417,6 @@ struct adouble_fops { #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_getentryoff(ad,eid) ((ad)->ad_eid[(eid)].ade_off) -#define ad_entry(ad,eid) ((caddr_t)(ad)->ad_data + (ad)->ad_eid[(eid)].ade_off) #define ad_get_HF_flags(ad) ((ad)->ad_resource_fork.adf_flags) #define ad_get_MD_flags(ad) ((ad)->ad_md->adf_flags) @@ -448,6 +448,7 @@ extern int ad_excl_lock (struct adouble * /*adp*/, const u_int32_t /*eid*/); #define ad_unlock ad_fcntl_unlock /* ad_open.c */ +extern void *ad_entry(const struct adouble *ad, int eid); extern int ad_setfuid (const uid_t ); extern uid_t ad_getfuid (void ); extern char *ad_dir (const char *); diff --git a/libatalk/adouble/ad_open.c b/libatalk/adouble/ad_open.c index 5f5a6b90330b..390e095cbd81 100644 --- a/libatalk/adouble/ad_open.c +++ b/libatalk/adouble/ad_open.c @@ -521,6 +521,7 @@ static int parse_entries(struct adouble *ad, uint16_t nentries, } } + ad->valid_data_len = valid_data_len; return 0; } @@ -754,6 +755,122 @@ static int ad_header_sfm_read(struct adouble *ad, struct stat *hst) return 0; } +/* + * 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; +} + /* --------------------------------------- * Put the .AppleDouble where it needs to be: *