b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | From 687106e4269c2e926e0452a88c2d72ac95779a95 Mon Sep 17 00:00:00 2001 |
| 2 | From: Andrew Walker <awalker@ixsystems.com> |
| 3 | Date: Mon, 11 Apr 2022 09:13:09 -0400 |
| 4 | Subject: [PATCH 1/4] add handling for cases where ad_entry() returns NULL |
| 5 | |
| 6 | With recent CVE fixes, ad_enty() may now return NULL. This |
| 7 | commit adds basic error handling for these cases and asserting |
| 8 | where such a return is totally unexpected. In case of |
| 9 | ad_getid() and ad_forcegetid(), return CNID_INVALID rather |
| 10 | than 0 to clarify for future people investigating this that |
| 11 | a 0 here is an indication of error. |
| 12 | |
| 13 | In case of new_ad_header(), the valid_data_len of the |
| 14 | adouble data may still be zero. This causes subsequent |
| 15 | ad_entry() calls within new_ad_header() to fail. As such, |
| 16 | overwrite valid_data-Len with AD_DATASZ2 or AD_DATASZ_EA |
| 17 | depending on how adouble data is stored on disk. |
| 18 | |
| 19 | Another side-effect of the fix is that ad_entry() for |
| 20 | ADEID_DID on ea-backed adouble data will return NULL. In |
| 21 | this case, add explicit check for the backend before |
| 22 | attempting to get the DID. |
| 23 | |
| 24 | Signed-off-by: Andrew Walker <awalker@ixsystems.com> |
| 25 | --- |
| 26 | etc/afpd/directory.c | 15 +++- |
| 27 | etc/afpd/file.c | 25 +++++-- |
| 28 | etc/afpd/volume.c | 7 +- |
| 29 | etc/cnid_dbd/cmd_dbd_scanvol.c | 9 ++- |
| 30 | libatalk/adouble/ad_attr.c | 130 +++++++++++++++++++++++++++------ |
| 31 | libatalk/adouble/ad_conv.c | 4 +- |
| 32 | libatalk/adouble/ad_date.c | 17 ++++- |
| 33 | libatalk/adouble/ad_flush.c | 27 ++++++- |
| 34 | libatalk/adouble/ad_open.c | 39 ++++++---- |
| 35 | 9 files changed, 215 insertions(+), 58 deletions(-) |
| 36 | |
| 37 | --- a/etc/afpd/directory.c |
| 38 | +++ b/etc/afpd/directory.c |
| 39 | @@ -1426,6 +1426,7 @@ int getdirparams(const AFPObj *obj, |
| 40 | struct maccess ma; |
| 41 | struct adouble ad; |
| 42 | char *data, *l_nameoff = NULL, *utf_nameoff = NULL; |
| 43 | + char *ade = NULL; |
| 44 | int bit = 0, isad = 0; |
| 45 | uint32_t aint; |
| 46 | uint16_t ashort; |
| 47 | @@ -1520,7 +1521,10 @@ int getdirparams(const AFPObj *obj, |
| 48 | |
| 49 | case DIRPBIT_FINFO : |
| 50 | if ( isad ) { |
| 51 | - memcpy( data, ad_entry( &ad, ADEID_FINDERI ), 32 ); |
| 52 | + ade = ad_entry(&ad, ADEID_FINDERI); |
| 53 | + AFP_ASSERT(ade != NULL); |
| 54 | + |
| 55 | + memcpy( data, ade, 32 ); |
| 56 | } else { /* no appledouble */ |
| 57 | memset( data, 0, 32 ); |
| 58 | /* dot files are by default visible */ |
| 59 | @@ -1744,6 +1748,7 @@ int setdirparams(struct vol *vol, struct |
| 60 | struct timeval tv; |
| 61 | |
| 62 | char *upath; |
| 63 | + char *ade = NULL; |
| 64 | struct dir *dir; |
| 65 | int bit, isad = 0; |
| 66 | int cdate, bdate; |
| 67 | @@ -1905,6 +1910,8 @@ int setdirparams(struct vol *vol, struct |
| 68 | fflags &= htons(~FINDERINFO_ISHARED); |
| 69 | memcpy(finder_buf + FINDERINFO_FRFLAGOFF, &fflags, sizeof(uint16_t)); |
| 70 | /* #2802236 end */ |
| 71 | + ade = ad_entry(&ad, ADEID_FINDERI); |
| 72 | + AFP_ASSERT(ade != NULL); |
| 73 | |
| 74 | if ( dir->d_did == DIRDID_ROOT ) { |
| 75 | /* |
| 76 | @@ -1915,10 +1922,10 @@ int setdirparams(struct vol *vol, struct |
| 77 | * behavior one sees when mounting above another mount |
| 78 | * point. |
| 79 | */ |
| 80 | - memcpy( ad_entry( &ad, ADEID_FINDERI ), finder_buf, 10 ); |
| 81 | - memcpy( ad_entry( &ad, ADEID_FINDERI ) + 14, finder_buf + 14, 18 ); |
| 82 | + memcpy( ade, finder_buf, 10 ); |
| 83 | + memcpy( ade + 14, finder_buf + 14, 18 ); |
| 84 | } else { |
| 85 | - memcpy( ad_entry( &ad, ADEID_FINDERI ), finder_buf, 32 ); |
| 86 | + memcpy( ade, finder_buf, 32 ); |
| 87 | } |
| 88 | } |
| 89 | break; |
| 90 | --- a/etc/afpd/file.c |
| 91 | +++ b/etc/afpd/file.c |
| 92 | @@ -296,6 +296,7 @@ int getmetadata(const AFPObj *obj, |
| 93 | { |
| 94 | char *data, *l_nameoff = NULL, *upath; |
| 95 | char *utf_nameoff = NULL; |
| 96 | + char *ade = NULL; |
| 97 | int bit = 0; |
| 98 | uint32_t aint; |
| 99 | cnid_t id = 0; |
| 100 | @@ -497,7 +498,10 @@ int getmetadata(const AFPObj *obj, |
| 101 | } |
| 102 | else { |
| 103 | if ( adp ) { |
| 104 | - memcpy(fdType, ad_entry( adp, ADEID_FINDERI ), 4 ); |
| 105 | + ade = ad_entry(adp, ADEID_FINDERI); |
| 106 | + AFP_ASSERT(ade != NULL); |
| 107 | + |
| 108 | + memcpy(fdType, ade, 4); |
| 109 | |
| 110 | if ( memcmp( fdType, "TEXT", 4 ) == 0 ) { |
| 111 | achar = '\x04'; |
| 112 | @@ -576,8 +580,19 @@ int getmetadata(const AFPObj *obj, |
| 113 | 10.3 clients freak out. */ |
| 114 | |
| 115 | aint = st->st_mode; |
| 116 | - if (adp) { |
| 117 | - memcpy(fdType, ad_entry( adp, ADEID_FINDERI ), 4 ); |
| 118 | + /* |
| 119 | + * ad_open() does not initialize adouble header |
| 120 | + * for symlinks. Hence this should be skipped to |
| 121 | + * avoid AFP_ASSERT here. Decision was made to |
| 122 | + * not alter ad_open() behavior so that |
| 123 | + * improper ops on symlink adoubles will be |
| 124 | + * more visible (assert). |
| 125 | + */ |
| 126 | + if (adp && (ad_meta_fileno(adp) != AD_SYMLINK)) { |
| 127 | + ade = ad_entry(adp, ADEID_FINDERI); |
| 128 | + AFP_ASSERT(ade != NULL); |
| 129 | + |
| 130 | + memcpy(fdType, ade, 4); |
| 131 | if ( memcmp( fdType, "slnk", 4 ) == 0 ) { |
| 132 | aint |= S_IFLNK; |
| 133 | } |
| 134 | @@ -839,6 +854,7 @@ int setfilparams(const AFPObj *obj, stru |
| 135 | struct extmap *em; |
| 136 | int bit, isad = 1, err = AFP_OK; |
| 137 | char *upath; |
| 138 | + char *ade = NULL; |
| 139 | u_char achar, *fdType, xyy[4]; /* uninitialized, OK 310105 */ |
| 140 | uint16_t ashort, bshort, oshort; |
| 141 | uint32_t aint; |
| 142 | @@ -989,7 +1005,7 @@ int setfilparams(const AFPObj *obj, stru |
| 143 | /* second try with adouble open |
| 144 | */ |
| 145 | if (ad_open(adp, upath, ADFLAGS_HF | ADFLAGS_RDWR | ADFLAGS_CREATE, 0666) < 0) { |
| 146 | - LOG(log_debug, logtype_afpd, "setfilparams: ad_open_metadata error"); |
| 147 | + LOG(log_debug, logtype_afpd, "setfilparams: ad_open_metadata error: %s", strerror(errno)); |
| 148 | /* |
| 149 | * For some things, we don't need an adouble header: |
| 150 | * - change of modification date |
| 151 | @@ -1021,6 +1037,9 @@ int setfilparams(const AFPObj *obj, stru |
| 152 | |
| 153 | switch( bit ) { |
| 154 | case FILPBIT_ATTR : |
| 155 | + if (isad == 0) { |
| 156 | + break; |
| 157 | + } |
| 158 | ad_getattr(adp, &bshort); |
| 159 | oshort = bshort; |
| 160 | if ( ntohs( ashort ) & ATTRBIT_SETCLR ) { |
| 161 | @@ -1034,15 +1053,26 @@ int setfilparams(const AFPObj *obj, stru |
| 162 | ad_setattr(adp, bshort); |
| 163 | break; |
| 164 | case FILPBIT_CDATE : |
| 165 | + if (isad == 0) { |
| 166 | + break; |
| 167 | + } |
| 168 | ad_setdate(adp, AD_DATE_CREATE, cdate); |
| 169 | break; |
| 170 | case FILPBIT_MDATE : |
| 171 | break; |
| 172 | case FILPBIT_BDATE : |
| 173 | + if (isad == 0) { |
| 174 | + break; |
| 175 | + } |
| 176 | ad_setdate(adp, AD_DATE_BACKUP, bdate); |
| 177 | break; |
| 178 | case FILPBIT_FINFO : |
| 179 | - if (default_type( ad_entry( adp, ADEID_FINDERI )) |
| 180 | + if (isad == 0) { |
| 181 | + break; |
| 182 | + } |
| 183 | + ade = ad_entry(adp, ADEID_FINDERI); |
| 184 | + AFP_ASSERT(ade != NULL); |
| 185 | + if (default_type(ade) |
| 186 | && ( |
| 187 | ((em = getextmap( path->m_name )) && |
| 188 | !memcmp(finder_buf, em->em_type, sizeof( em->em_type )) && |
| 189 | @@ -1053,7 +1083,7 @@ int setfilparams(const AFPObj *obj, stru |
| 190 | )) { |
| 191 | memcpy(finder_buf, ufinderi, 8 ); |
| 192 | } |
| 193 | - memcpy(ad_entry( adp, ADEID_FINDERI ), finder_buf, 32 ); |
| 194 | + memcpy(ade, finder_buf, 32 ); |
| 195 | break; |
| 196 | case FILPBIT_UNIXPR : |
| 197 | if (upriv_bit) { |
| 198 | @@ -1061,9 +1091,15 @@ int setfilparams(const AFPObj *obj, stru |
| 199 | } |
| 200 | break; |
| 201 | case FILPBIT_PDINFO : |
| 202 | + if (isad == 0) { |
| 203 | + break; |
| 204 | + } |
| 205 | + ade = ad_entry(adp, ADEID_FINDERI); |
| 206 | + AFP_ASSERT(ade != NULL); |
| 207 | + |
| 208 | if (obj->afp_version < 30) { /* else it's UTF8 name */ |
| 209 | - memcpy(ad_entry( adp, ADEID_FINDERI ), fdType, 4 ); |
| 210 | - memcpy(ad_entry( adp, ADEID_FINDERI ) + 4, "pdos", 4 ); |
| 211 | + memcpy(ade, fdType, 4 ); |
| 212 | + memcpy(ade + 4, "pdos", 4 ); |
| 213 | break; |
| 214 | } |
| 215 | /* fallthrough */ |
| 216 | --- a/etc/afpd/volume.c |
| 217 | +++ b/etc/afpd/volume.c |
| 218 | @@ -305,6 +305,7 @@ static int getvolparams(const AFPObj *ob |
| 219 | VolSpace xbfree, xbtotal; /* extended bytes */ |
| 220 | char *data, *nameoff = NULL; |
| 221 | char *slash; |
| 222 | + char *ade = NULL; |
| 223 | |
| 224 | LOG(log_debug, logtype_afpd, "getvolparams: Volume '%s'", vol->v_localname); |
| 225 | |
| 226 | @@ -328,8 +329,10 @@ static int getvolparams(const AFPObj *ob |
| 227 | slash = vol->v_path; |
| 228 | if (ad_getentryoff(&ad, ADEID_NAME)) { |
| 229 | ad_setentrylen( &ad, ADEID_NAME, strlen( slash )); |
| 230 | - memcpy(ad_entry( &ad, ADEID_NAME ), slash, |
| 231 | - ad_getentrylen( &ad, ADEID_NAME )); |
| 232 | + ade = ad_entry(&ad, ADEID_NAME); |
| 233 | + AFP_ASSERT(ade != NULL); |
| 234 | + |
| 235 | + memcpy(ade, slash, ad_getentrylen( &ad, ADEID_NAME )); |
| 236 | } |
| 237 | vol_setdate(vol->v_vid, &ad, st->st_mtime); |
| 238 | ad_flush(&ad); |
| 239 | --- a/etc/cnid_dbd/cmd_dbd_scanvol.c |
| 240 | +++ b/etc/cnid_dbd/cmd_dbd_scanvol.c |
| 241 | @@ -560,6 +560,7 @@ static int read_addir(void) |
| 242 | static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok) |
| 243 | { |
| 244 | int adflags = ADFLAGS_HF; |
| 245 | + int err; |
| 246 | cnid_t db_cnid, ad_cnid; |
| 247 | struct adouble ad; |
| 248 | |
| 249 | @@ -602,7 +603,13 @@ static cnid_t check_cnid(const char *nam |
| 250 | cwdbuf, name, strerror(errno)); |
| 251 | return CNID_INVALID; |
| 252 | } |
| 253 | - ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp); |
| 254 | + err = ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp); |
| 255 | + if (err == -1) { |
| 256 | + dbd_log(LOGSTD, "Error setting new CNID, malformed adouble: '%s/%s'", |
| 257 | + cwdbuf, name); |
| 258 | + ad_close(&ad, ADFLAGS_HF); |
| 259 | + return CNID_INVALID; |
| 260 | + } |
| 261 | ad_flush(&ad); |
| 262 | ad_close(&ad, ADFLAGS_HF); |
| 263 | } |
| 264 | --- a/libatalk/adouble/ad_attr.c |
| 265 | +++ b/libatalk/adouble/ad_attr.c |
| 266 | @@ -2,8 +2,10 @@ |
| 267 | #include "config.h" |
| 268 | #endif /* HAVE_CONFIG_H */ |
| 269 | |
| 270 | +#include <stdlib.h> |
| 271 | #include <string.h> |
| 272 | #include <arpa/inet.h> |
| 273 | +#include <atalk/util.h> |
| 274 | #include <atalk/adouble.h> |
| 275 | #include <atalk/logger.h> |
| 276 | |
| 277 | @@ -22,10 +24,17 @@ int ad_getattr(const struct adouble *ad, |
| 278 | *attr = 0; |
| 279 | |
| 280 | if (ad_getentryoff(ad, ADEID_AFPFILEI)) { |
| 281 | - memcpy(attr, ad_entry(ad, ADEID_AFPFILEI) + AFPFILEIOFF_ATTR, 2); |
| 282 | + char *adp = NULL; |
| 283 | + |
| 284 | + adp = ad_entry(ad, ADEID_AFPFILEI); |
| 285 | + AFP_ASSERT(adp != NULL); |
| 286 | + memcpy(attr, adp + AFPFILEIOFF_ATTR, 2); |
| 287 | |
| 288 | /* Now get opaque flags from FinderInfo */ |
| 289 | - memcpy(&fflags, ad_entry(ad, ADEID_FINDERI) + FINDERINFO_FRFLAGOFF, 2); |
| 290 | + adp = ad_entry(ad, ADEID_FINDERI); |
| 291 | + AFP_ASSERT(adp != NULL); |
| 292 | + memcpy(&fflags, adp + FINDERINFO_FRFLAGOFF, 2); |
| 293 | + |
| 294 | if (fflags & htons(FINDERINFO_INVISIBLE)) |
| 295 | *attr |= htons(ATTRBIT_INVISIBLE); |
| 296 | else |
| 297 | @@ -61,10 +70,15 @@ int ad_setattr(const struct adouble *ad, |
| 298 | attr &= ~(ATTRBIT_MULTIUSER | ATTRBIT_NOWRITE | ATTRBIT_NOCOPY); |
| 299 | |
| 300 | if (ad_getentryoff(ad, ADEID_AFPFILEI) && ad_getentryoff(ad, ADEID_FINDERI)) { |
| 301 | - memcpy(ad_entry(ad, ADEID_AFPFILEI) + AFPFILEIOFF_ATTR, &attr, sizeof(attr)); |
| 302 | + char *adp = NULL; |
| 303 | + |
| 304 | + adp = ad_entry(ad, ADEID_FINDERI); |
| 305 | + AFP_ASSERT(adp != NULL); |
| 306 | + |
| 307 | + memcpy(adp + AFPFILEIOFF_ATTR, &attr, sizeof(attr)); |
| 308 | |
| 309 | /* Now set opaque flags in FinderInfo too */ |
| 310 | - memcpy(&fflags, ad_entry(ad, ADEID_FINDERI) + FINDERINFO_FRFLAGOFF, 2); |
| 311 | + memcpy(&fflags, adp + FINDERINFO_FRFLAGOFF, 2); |
| 312 | if (attr & htons(ATTRBIT_INVISIBLE)) |
| 313 | fflags |= htons(FINDERINFO_INVISIBLE); |
| 314 | else |
| 315 | @@ -77,7 +91,7 @@ int ad_setattr(const struct adouble *ad, |
| 316 | } else |
| 317 | fflags &= htons(~FINDERINFO_ISHARED); |
| 318 | |
| 319 | - memcpy(ad_entry(ad, ADEID_FINDERI) + FINDERINFO_FRFLAGOFF, &fflags, 2); |
| 320 | + memcpy(adp + FINDERINFO_FRFLAGOFF, &fflags, 2); |
| 321 | } |
| 322 | |
| 323 | return 0; |
| 324 | @@ -86,54 +100,114 @@ int ad_setattr(const struct adouble *ad, |
| 325 | /* -------------- |
| 326 | * save file/folder ID in AppleDoubleV2 netatalk private parameters |
| 327 | * return 1 if resource fork has been modified |
| 328 | + * return -1 on error. |
| 329 | */ |
| 330 | int ad_setid (struct adouble *adp, const dev_t dev, const ino_t ino , const uint32_t id, const cnid_t did, const void *stamp) |
| 331 | { |
| 332 | uint32_t tmp; |
| 333 | + char *ade = NULL; |
| 334 | |
| 335 | ad_setentrylen( adp, ADEID_PRIVID, sizeof(id)); |
| 336 | tmp = id; |
| 337 | if (adp->ad_vers == AD_VERSION_EA) |
| 338 | tmp = htonl(tmp); |
| 339 | - memcpy(ad_entry( adp, ADEID_PRIVID ), &tmp, sizeof(tmp)); |
| 340 | + |
| 341 | + ade = ad_entry(adp, ADEID_PRIVID); |
| 342 | + if (ade == NULL) { |
| 343 | + LOG(log_warning, logtype_ad, "ad_setid: failed to set ADEID_PRIVID\n"); |
| 344 | + return -1; |
| 345 | + } |
| 346 | + memcpy(ade, &tmp, sizeof(tmp)); |
| 347 | |
| 348 | ad_setentrylen( adp, ADEID_PRIVDEV, sizeof(dev_t)); |
| 349 | + ade = ad_entry(adp, ADEID_PRIVDEV); |
| 350 | + if (ade == NULL) { |
| 351 | + LOG(log_warning, logtype_ad, "ad_setid: failed to set ADEID_PRIVDEV\n"); |
| 352 | + return -1; |
| 353 | + } |
| 354 | + |
| 355 | if ((adp->ad_options & ADVOL_NODEV)) { |
| 356 | - memset(ad_entry( adp, ADEID_PRIVDEV ), 0, sizeof(dev_t)); |
| 357 | + memset(ade, 0, sizeof(dev_t)); |
| 358 | } else { |
| 359 | - memcpy(ad_entry( adp, ADEID_PRIVDEV ), &dev, sizeof(dev_t)); |
| 360 | + memcpy(ade, &dev, sizeof(dev_t)); |
| 361 | } |
| 362 | |
| 363 | ad_setentrylen( adp, ADEID_PRIVINO, sizeof(ino_t)); |
| 364 | - memcpy(ad_entry( adp, ADEID_PRIVINO ), &ino, sizeof(ino_t)); |
| 365 | |
| 366 | - ad_setentrylen( adp, ADEID_DID, sizeof(did)); |
| 367 | - memcpy(ad_entry( adp, ADEID_DID ), &did, sizeof(did)); |
| 368 | + ade = ad_entry(adp, ADEID_PRIVINO); |
| 369 | + if (ade == NULL) { |
| 370 | + LOG(log_warning, logtype_ad, "ad_setid: failed to set ADEID_PRIVINO\n"); |
| 371 | + return -1; |
| 372 | + } |
| 373 | + memcpy(ade, &ino, sizeof(ino_t)); |
| 374 | + |
| 375 | + if (adp->ad_vers != AD_VERSION_EA) { |
| 376 | + ad_setentrylen( adp, ADEID_DID, sizeof(did)); |
| 377 | + |
| 378 | + ade = ad_entry(adp, ADEID_DID); |
| 379 | + if (ade == NULL) { |
| 380 | + LOG(log_warning, logtype_ad, "ad_setid: failed to set ADEID_DID\n"); |
| 381 | + return -1; |
| 382 | + } |
| 383 | + memcpy(ade, &did, sizeof(did)); |
| 384 | + } |
| 385 | |
| 386 | ad_setentrylen( adp, ADEID_PRIVSYN, ADEDLEN_PRIVSYN); |
| 387 | - memcpy(ad_entry( adp, ADEID_PRIVSYN ), stamp, ADEDLEN_PRIVSYN); |
| 388 | + ade = ad_entry(adp, ADEID_PRIVSYN); |
| 389 | + if (ade == NULL) { |
| 390 | + LOG(log_warning, logtype_ad, "ad_setid: failed to set ADEID_PRIVSYN\n"); |
| 391 | + return -1; |
| 392 | + } |
| 393 | + memcpy(ade, stamp, ADEDLEN_PRIVSYN); |
| 394 | |
| 395 | return 1; |
| 396 | } |
| 397 | |
| 398 | -/* ----------------------------- */ |
| 399 | +/* |
| 400 | + * Retrieve stored file / folder. Callers should treat a return of CNID_INVALID (0) as an invalid value. |
| 401 | + */ |
| 402 | uint32_t ad_getid (struct adouble *adp, const dev_t st_dev, const ino_t st_ino , const cnid_t did, const void *stamp _U_) |
| 403 | { |
| 404 | uint32_t aint = 0; |
| 405 | dev_t dev; |
| 406 | ino_t ino; |
| 407 | - cnid_t a_did; |
| 408 | + cnid_t a_did = 0; |
| 409 | |
| 410 | if (adp) { |
| 411 | if (sizeof(dev_t) == ad_getentrylen(adp, ADEID_PRIVDEV)) { |
| 412 | - memcpy(&dev, ad_entry(adp, ADEID_PRIVDEV), sizeof(dev_t)); |
| 413 | - memcpy(&ino, ad_entry(adp, ADEID_PRIVINO), sizeof(ino_t)); |
| 414 | - memcpy(&a_did, ad_entry(adp, ADEID_DID), sizeof(cnid_t)); |
| 415 | + char *ade = NULL; |
| 416 | + ade = ad_entry(adp, ADEID_PRIVDEV); |
| 417 | + if (ade == NULL) { |
| 418 | + LOG(log_warning, logtype_ad, "ad_getid: failed to retrieve ADEID_PRIVDEV\n"); |
| 419 | + return CNID_INVALID; |
| 420 | + } |
| 421 | + memcpy(&dev, ade, sizeof(dev_t)); |
| 422 | + ade = ad_entry(adp, ADEID_PRIVINO); |
| 423 | + if (ade == NULL) { |
| 424 | + LOG(log_warning, logtype_ad, "ad_getid: failed to retrieve ADEID_PRIVINO\n"); |
| 425 | + return CNID_INVALID; |
| 426 | + } |
| 427 | + memcpy(&ino, ade, sizeof(ino_t)); |
| 428 | + |
| 429 | + if (adp->ad_vers != AD_VERSION_EA) { |
| 430 | + /* ADEID_DID is not stored for AD_VERSION_EA */ |
| 431 | + ade = ad_entry(adp, ADEID_DID); |
| 432 | + if (ade == NULL) { |
| 433 | + LOG(log_warning, logtype_ad, "ad_getid: failed to retrieve ADEID_DID\n"); |
| 434 | + return CNID_INVALID; |
| 435 | + } |
| 436 | + memcpy(&a_did, ade, sizeof(cnid_t)); |
| 437 | + } |
| 438 | |
| 439 | if (((adp->ad_options & ADVOL_NODEV) || (dev == st_dev)) |
| 440 | && ino == st_ino |
| 441 | - && (!did || a_did == did) ) { |
| 442 | - memcpy(&aint, ad_entry(adp, ADEID_PRIVID), sizeof(aint)); |
| 443 | + && (!did || a_did == 0 || a_did == did) ) { |
| 444 | + ade = ad_entry(adp, ADEID_PRIVID); |
| 445 | + if (ade == NULL) { |
| 446 | + LOG(log_warning, logtype_ad, "ad_getid: failed to retrieve ADEID_PRIVID\n"); |
| 447 | + return CNID_INVALID; |
| 448 | + } |
| 449 | + memcpy(&aint, ade, sizeof(aint)); |
| 450 | if (adp->ad_vers == AD_VERSION2) |
| 451 | return aint; |
| 452 | else |
| 453 | @@ -141,7 +215,7 @@ uint32_t ad_getid (struct adouble *adp, |
| 454 | } |
| 455 | } |
| 456 | } |
| 457 | - return 0; |
| 458 | + return CNID_INVALID; |
| 459 | } |
| 460 | |
| 461 | /* ----------------------------- */ |
| 462 | @@ -150,13 +224,18 @@ uint32_t ad_forcegetid (struct adouble * |
| 463 | uint32_t aint = 0; |
| 464 | |
| 465 | if (adp) { |
| 466 | - memcpy(&aint, ad_entry(adp, ADEID_PRIVID), sizeof(aint)); |
| 467 | + char *ade = NULL; |
| 468 | + ade = ad_entry(adp, ADEID_PRIVID); |
| 469 | + if (ade == NULL) { |
| 470 | + return CNID_INVALID; |
| 471 | + } |
| 472 | + memcpy(&aint, ade, sizeof(aint)); |
| 473 | if (adp->ad_vers == AD_VERSION2) |
| 474 | return aint; |
| 475 | else |
| 476 | return ntohl(aint); |
| 477 | } |
| 478 | - return 0; |
| 479 | + return CNID_INVALID; |
| 480 | } |
| 481 | |
| 482 | /* ----------------- |
| 483 | @@ -168,8 +247,13 @@ int ad_setname(struct adouble *ad, const |
| 484 | if ((len = strlen(path)) > ADEDLEN_NAME) |
| 485 | len = ADEDLEN_NAME; |
| 486 | if (path && ad_getentryoff(ad, ADEID_NAME)) { |
| 487 | + char *ade = NULL; |
| 488 | ad_setentrylen( ad, ADEID_NAME, len); |
| 489 | - memcpy(ad_entry( ad, ADEID_NAME ), path, len); |
| 490 | + ade = ad_entry(ad, ADEID_NAME); |
| 491 | + if (ade == NULL) { |
| 492 | + return -1; |
| 493 | + } |
| 494 | + memcpy(ade, path, len); |
| 495 | return 1; |
| 496 | } |
| 497 | return 0; |
| 498 | --- a/libatalk/adouble/ad_conv.c |
| 499 | +++ b/libatalk/adouble/ad_conv.c |
| 500 | @@ -93,6 +93,7 @@ static int ad_conv_v22ea_hf(const char * |
| 501 | goto copy; |
| 502 | if (ad_getentryoff(&adv2, ADEID_FINDERI) |
| 503 | && (ad_getentrylen(&adv2, ADEID_FINDERI) == ADEDLEN_FINDERI) |
| 504 | + && (ad_entry(&adv2, ADEID_FINDERI) != NULL) |
| 505 | && (memcmp(ad_entry(&adv2, ADEID_FINDERI), emptyad, ADEDLEN_FINDERI) != 0)) |
| 506 | goto copy; |
| 507 | if (ad_getentryoff(&adv2, ADEID_FILEDATESI)) { |
| 508 | @@ -101,7 +102,7 @@ static int ad_conv_v22ea_hf(const char * |
| 509 | if ((ctime != mtime) || (mtime != sp->st_mtime)) |
| 510 | goto copy; |
| 511 | } |
| 512 | - if (ad_getentryoff(&adv2, ADEID_AFPFILEI)) { |
| 513 | + if (ad_getentryoff(&adv2, ADEID_AFPFILEI) && (ad_entry(&adv2, ADEID_AFPFILEI) != NULL)) { |
| 514 | if (memcmp(ad_entry(&adv2, ADEID_AFPFILEI), &afpinfo, ADEDLEN_AFPFILEI) != 0) |
| 515 | goto copy; |
| 516 | } |
| 517 | @@ -115,6 +116,7 @@ copy: |
| 518 | EC_ZERO_LOGSTR( ad_open(&adea, path, adflags | ADFLAGS_HF | ADFLAGS_RDWR | ADFLAGS_CREATE), |
| 519 | "ad_conv_v22ea_hf(\"%s\"): error creating metadata EA: %s", |
| 520 | fullpathname(path), strerror(errno)); |
| 521 | + AFP_ASSERT(ad_refresh(path, &adea) == 0); |
| 522 | EC_ZERO_LOG( ad_copy_header(&adea, &adv2) ); |
| 523 | ad_flush(&adea); |
| 524 | |
| 525 | --- a/libatalk/adouble/ad_date.c |
| 526 | +++ b/libatalk/adouble/ad_date.c |
| 527 | @@ -10,6 +10,7 @@ int ad_setdate(struct adouble *ad, |
| 528 | unsigned int dateoff, uint32_t date) |
| 529 | { |
| 530 | int xlate = (dateoff & AD_DATE_UNIX); |
| 531 | + char *ade = NULL; |
| 532 | |
| 533 | dateoff &= AD_DATE_MASK; |
| 534 | if (xlate) |
| 535 | @@ -20,7 +21,12 @@ int ad_setdate(struct adouble *ad, |
| 536 | |
| 537 | if (dateoff > AD_DATE_ACCESS) |
| 538 | return -1; |
| 539 | - memcpy(ad_entry(ad, ADEID_FILEDATESI) + dateoff, &date, sizeof(date)); |
| 540 | + |
| 541 | + ade = ad_entry(ad, ADEID_FILEDATESI); |
| 542 | + if (ade == NULL) { |
| 543 | + return -1; |
| 544 | + } |
| 545 | + memcpy(ade + dateoff, &date, sizeof(date)); |
| 546 | |
| 547 | return 0; |
| 548 | } |
| 549 | @@ -29,6 +35,7 @@ int ad_getdate(const struct adouble *ad, |
| 550 | unsigned int dateoff, uint32_t *date) |
| 551 | { |
| 552 | int xlate = (dateoff & AD_DATE_UNIX); |
| 553 | + char *ade = NULL; |
| 554 | |
| 555 | dateoff &= AD_DATE_MASK; |
| 556 | if (!ad_getentryoff(ad, ADEID_FILEDATESI)) |
| 557 | @@ -36,7 +43,13 @@ int ad_getdate(const struct adouble *ad, |
| 558 | |
| 559 | if (dateoff > AD_DATE_ACCESS) |
| 560 | return -1; |
| 561 | - memcpy(date, ad_entry(ad, ADEID_FILEDATESI) + dateoff, sizeof(uint32_t)); |
| 562 | + |
| 563 | + |
| 564 | + ade = ad_entry(ad, ADEID_FILEDATESI); |
| 565 | + if (ade == NULL) { |
| 566 | + return -1; |
| 567 | + } |
| 568 | + memcpy(date, ade + dateoff, sizeof(uint32_t)); |
| 569 | |
| 570 | if (xlate) |
| 571 | *date = AD_DATE_TO_UNIX(*date); |
| 572 | --- a/libatalk/adouble/ad_flush.c |
| 573 | +++ b/libatalk/adouble/ad_flush.c |
| 574 | @@ -151,6 +151,7 @@ int ad_rebuild_adouble_header_osx(struct |
| 575 | uint32_t temp; |
| 576 | uint16_t nent; |
| 577 | char *buf; |
| 578 | + char *ade = NULL; |
| 579 | |
| 580 | LOG(log_debug, logtype_ad, "ad_rebuild_adouble_header_osx"); |
| 581 | |
| 582 | @@ -184,7 +185,10 @@ int ad_rebuild_adouble_header_osx(struct |
| 583 | memcpy(buf, &temp, sizeof( temp )); |
| 584 | buf += sizeof( temp ); |
| 585 | |
| 586 | - memcpy(adbuf + ADEDOFF_FINDERI_OSX, ad_entry(ad, ADEID_FINDERI), ADEDLEN_FINDERI); |
| 587 | + ade = ad_entry(ad, ADEID_FINDERI); |
| 588 | + AFP_ASSERT(ade != NULL); |
| 589 | + |
| 590 | + memcpy(adbuf + ADEDOFF_FINDERI_OSX, ade, ADEDLEN_FINDERI); |
| 591 | |
| 592 | /* rfork */ |
| 593 | temp = htonl( EID_DISK(ADEID_RFORK) ); |
| 594 | @@ -211,8 +215,12 @@ int ad_copy_header(struct adouble *add, |
| 595 | { |
| 596 | uint32_t eid; |
| 597 | uint32_t len; |
| 598 | + char *src = NULL; |
| 599 | + char *dst = NULL; |
| 600 | |
| 601 | for ( eid = 0; eid < ADEID_MAX; eid++ ) { |
| 602 | + src = dst = NULL; |
| 603 | + |
| 604 | if ( ads->ad_eid[ eid ].ade_off == 0 || add->ad_eid[ eid ].ade_off == 0 ) |
| 605 | continue; |
| 606 | |
| 607 | @@ -226,17 +234,28 @@ int ad_copy_header(struct adouble *add, |
| 608 | continue; |
| 609 | default: |
| 610 | ad_setentrylen( add, eid, len ); |
| 611 | - memcpy( ad_entry( add, eid ), ad_entry( ads, eid ), len ); |
| 612 | + dst = ad_entry(add, eid); |
| 613 | + AFP_ASSERT(dst != NULL); |
| 614 | + |
| 615 | + src = ad_entry(ads, eid); |
| 616 | + AFP_ASSERT(src != NULL); |
| 617 | + |
| 618 | + memcpy( dst, src, len ); |
| 619 | } |
| 620 | } |
| 621 | add->ad_rlen = ads->ad_rlen; |
| 622 | |
| 623 | if (((ads->ad_vers == AD_VERSION2) && (add->ad_vers == AD_VERSION_EA)) |
| 624 | || ((ads->ad_vers == AD_VERSION_EA) && (add->ad_vers == AD_VERSION2))) { |
| 625 | + src = dst = NULL; |
| 626 | cnid_t id; |
| 627 | - memcpy(&id, ad_entry(add, ADEID_PRIVID), sizeof(cnid_t)); |
| 628 | + |
| 629 | + dst = ad_entry(add, ADEID_PRIVID); |
| 630 | + AFP_ASSERT(dst != NULL); |
| 631 | + |
| 632 | + memcpy(&id, dst, sizeof(cnid_t)); |
| 633 | id = htonl(id); |
| 634 | - memcpy(ad_entry(add, ADEID_PRIVID), &id, sizeof(cnid_t)); |
| 635 | + memcpy(dst, &id, sizeof(cnid_t)); |
| 636 | } |
| 637 | return 0; |
| 638 | } |
| 639 | --- a/libatalk/adouble/ad_open.c |
| 640 | +++ b/libatalk/adouble/ad_open.c |
| 641 | @@ -140,17 +140,17 @@ static struct adouble_fops ad_adouble_ea |
| 642 | |
| 643 | static const struct entry entry_order2[ADEID_NUM_V2 + 1] = { |
| 644 | {ADEID_NAME, ADEDOFF_NAME_V2, ADEDLEN_INIT}, |
| 645 | - {ADEID_COMMENT, ADEDOFF_COMMENT_V2, ADEDLEN_INIT}, |
| 646 | + {ADEID_COMMENT, ADEDOFF_COMMENT_V2, ADEDLEN_COMMENT}, |
| 647 | {ADEID_FILEDATESI, ADEDOFF_FILEDATESI, ADEDLEN_FILEDATESI}, |
| 648 | {ADEID_FINDERI, ADEDOFF_FINDERI_V2, ADEDLEN_FINDERI}, |
| 649 | {ADEID_DID, ADEDOFF_DID, ADEDLEN_DID}, |
| 650 | {ADEID_AFPFILEI, ADEDOFF_AFPFILEI, ADEDLEN_AFPFILEI}, |
| 651 | {ADEID_SHORTNAME, ADEDOFF_SHORTNAME, ADEDLEN_INIT}, |
| 652 | {ADEID_PRODOSFILEI, ADEDOFF_PRODOSFILEI, ADEDLEN_PRODOSFILEI}, |
| 653 | - {ADEID_PRIVDEV, ADEDOFF_PRIVDEV, ADEDLEN_INIT}, |
| 654 | - {ADEID_PRIVINO, ADEDOFF_PRIVINO, ADEDLEN_INIT}, |
| 655 | - {ADEID_PRIVSYN, ADEDOFF_PRIVSYN, ADEDLEN_INIT}, |
| 656 | - {ADEID_PRIVID, ADEDOFF_PRIVID, ADEDLEN_INIT}, |
| 657 | + {ADEID_PRIVDEV, ADEDOFF_PRIVDEV, ADEDLEN_PRIVDEV}, |
| 658 | + {ADEID_PRIVINO, ADEDOFF_PRIVINO, ADEDLEN_PRIVINO}, |
| 659 | + {ADEID_PRIVSYN, ADEDOFF_PRIVSYN, ADEDLEN_PRIVSYN}, |
| 660 | + {ADEID_PRIVID, ADEDOFF_PRIVID, ADEDLEN_PRIVID}, |
| 661 | {ADEID_RFORK, ADEDOFF_RFORK_V2, ADEDLEN_INIT}, |
| 662 | {0, 0, 0} |
| 663 | }; |
| 664 | @@ -158,13 +158,13 @@ static const struct entry entry_order2[A |
| 665 | /* Using Extended Attributes */ |
| 666 | static const struct entry entry_order_ea[ADEID_NUM_EA + 1] = { |
| 667 | {ADEID_FINDERI, ADEDOFF_FINDERI_EA, ADEDLEN_FINDERI}, |
| 668 | - {ADEID_COMMENT, ADEDOFF_COMMENT_EA, ADEDLEN_INIT}, |
| 669 | + {ADEID_COMMENT, ADEDOFF_COMMENT_EA, ADEDLEN_COMMENT}, |
| 670 | {ADEID_FILEDATESI, ADEDOFF_FILEDATESI_EA, ADEDLEN_FILEDATESI}, |
| 671 | {ADEID_AFPFILEI, ADEDOFF_AFPFILEI_EA, ADEDLEN_AFPFILEI}, |
| 672 | - {ADEID_PRIVDEV, ADEDOFF_PRIVDEV_EA, ADEDLEN_INIT}, |
| 673 | - {ADEID_PRIVINO, ADEDOFF_PRIVINO_EA, ADEDLEN_INIT}, |
| 674 | - {ADEID_PRIVSYN, ADEDOFF_PRIVSYN_EA, ADEDLEN_INIT}, |
| 675 | - {ADEID_PRIVID, ADEDOFF_PRIVID_EA, ADEDLEN_INIT}, |
| 676 | + {ADEID_PRIVDEV, ADEDOFF_PRIVDEV_EA, ADEDLEN_PRIVDEV}, |
| 677 | + {ADEID_PRIVINO, ADEDOFF_PRIVINO_EA, ADEDLEN_PRIVINO}, |
| 678 | + {ADEID_PRIVSYN, ADEDOFF_PRIVSYN_EA, ADEDLEN_PRIVSYN}, |
| 679 | + {ADEID_PRIVID, ADEDOFF_PRIVID_EA, ADEDLEN_PRIVID}, |
| 680 | {0, 0, 0} |
| 681 | }; |
| 682 | |
| 683 | @@ -360,15 +360,22 @@ static int new_ad_header(struct adouble |
| 684 | const struct entry *eid; |
| 685 | uint16_t ashort; |
| 686 | struct stat st; |
| 687 | + char *adp = NULL; |
| 688 | |
| 689 | LOG(log_debug, logtype_ad, "new_ad_header(\"%s\")", path); |
| 690 | |
| 691 | if (ad_init_offsets(ad) != 0) |
| 692 | return -1; |
| 693 | |
| 694 | + if (ad->valid_data_len == 0) { |
| 695 | + ad->valid_data_len = ad->ad_vers == AD_VERSION_EA ? AD_DATASZ_EA : AD_DATASZ2; |
| 696 | + } |
| 697 | + adp = ad_entry(ad, ADEID_FINDERI); |
| 698 | + AFP_ASSERT(adp != NULL); |
| 699 | + |
| 700 | /* set default creator/type fields */ |
| 701 | - memcpy(ad_entry(ad, ADEID_FINDERI) + FINDERINFO_FRTYPEOFF,"\0\0\0\0", 4); |
| 702 | - memcpy(ad_entry(ad, ADEID_FINDERI) + FINDERINFO_FRCREATOFF,"\0\0\0\0", 4); |
| 703 | + memcpy(adp + FINDERINFO_FRTYPEOFF,"\0\0\0\0", 4); |
| 704 | + memcpy(adp + FINDERINFO_FRCREATOFF,"\0\0\0\0", 4); |
| 705 | |
| 706 | /* make things invisible */ |
| 707 | if ((ad->ad_options & ADVOL_INVDOTS) |
| 708 | @@ -378,14 +385,16 @@ static int new_ad_header(struct adouble |
| 709 | ashort = htons(ATTRBIT_INVISIBLE); |
| 710 | ad_setattr(ad, ashort); |
| 711 | ashort = htons(FINDERINFO_INVISIBLE); |
| 712 | - memcpy(ad_entry(ad, ADEID_FINDERI) + FINDERINFO_FRFLAGOFF, &ashort, sizeof(ashort)); |
| 713 | + memcpy(adp + FINDERINFO_FRFLAGOFF, &ashort, sizeof(ashort)); |
| 714 | } |
| 715 | |
| 716 | /* put something sane in the date fields */ |
| 717 | if (stp == NULL) { |
| 718 | stp = &st; |
| 719 | - if (lstat(path, &st) != 0) |
| 720 | + if (lstat(path, &st) != 0) { |
| 721 | + ad->valid_data_len = 0; |
| 722 | return -1; |
| 723 | + } |
| 724 | } |
| 725 | ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, stp->st_mtime); |
| 726 | ad_setdate(ad, AD_DATE_MODIFY | AD_DATE_UNIX, stp->st_mtime); |
| 727 | @@ -417,7 +426,7 @@ static int parse_entries(struct adouble |
| 728 | |
| 729 | if (!eid |
| 730 | || eid > ADEID_MAX |
| 731 | - || off >= valid_data_len |
| 732 | + || ((eid != ADEID_RFORK) && (off >= valid_data_len)) |
| 733 | || ((eid != ADEID_RFORK) && (off + len > valid_data_len))) |
| 734 | { |
| 735 | LOG(log_warning, logtype_ad, "parse_entries: bogus eid: %u, off: %u, len: %u", |
| 736 | @@ -782,16 +791,41 @@ static int ad_header_read_ea(const char |
| 737 | EC_FAIL; |
| 738 | } |
| 739 | |
| 740 | + /* |
| 741 | + * It is possible for AFP metadata to contain a zero-length |
| 742 | + * comment. This will cause ad_entry(ad, ADEID_COMMENT) to return NULL |
| 743 | + * but should not be treated as an error condition. |
| 744 | + * Since recent CVE fixes have introduced new behavior regarding |
| 745 | + * ad_entry() output. For now, we will AFP_ASSERT() in EC_CLEANUP to prevent |
| 746 | + * altering on-disk info. This does introduce an avenue to DOS |
| 747 | + * the netatalk server by locally writing garbage to the EA. At this |
| 748 | + * point, the outcome is an acceptable risk to prevent unintended |
| 749 | + * changes to metadata. |
| 750 | + */ |
| 751 | if (nentries != ADEID_NUM_EA |
| 752 | || !ad_entry(ad, ADEID_FINDERI) |
| 753 | +#if 0 |
| 754 | || !ad_entry(ad, ADEID_COMMENT) |
| 755 | +#endif |
| 756 | || !ad_entry(ad, ADEID_FILEDATESI) |
| 757 | || !ad_entry(ad, ADEID_AFPFILEI) |
| 758 | || !ad_entry(ad, ADEID_PRIVDEV) |
| 759 | || !ad_entry(ad, ADEID_PRIVINO) |
| 760 | || !ad_entry(ad, ADEID_PRIVSYN) |
| 761 | || !ad_entry(ad, ADEID_PRIVID)) { |
| 762 | - LOG(log_error, logtype_ad, "ad_header_read_ea(\"%s\"): invalid metadata EA", fullpathname(path)); |
| 763 | + LOG(log_error, logtype_ad, |
| 764 | + "ad_header_read_ea(\"%s\"): invalid metadata EA " |
| 765 | + "this is now being treated as a fatal error. " |
| 766 | + "if you see this log entry, please file a bug ticket " |
| 767 | + "with your upstream vendor and attach the generated " |
| 768 | + "core file.", path ? fullpathname(path) : "UNKNOWN"); |
| 769 | + |
| 770 | + errno = EINVAL; |
| 771 | + EC_FAIL; |
| 772 | + } |
| 773 | + |
| 774 | + if (!ad_entry(ad, ADEID_COMMENT) && |
| 775 | + (ad->ad_eid[ADEID_COMMENT].ade_len != 0)) { |
| 776 | errno = EINVAL; |
| 777 | EC_FAIL; |
| 778 | } |
| 779 | @@ -805,6 +839,8 @@ static int ad_header_read_ea(const char |
| 780 | #endif |
| 781 | |
| 782 | EC_CLEANUP: |
| 783 | + AFP_ASSERT(!(ret != 0 && errno == EINVAL)); |
| 784 | +#if 0 |
| 785 | if (ret != 0 && errno == EINVAL) { |
| 786 | become_root(); |
| 787 | (void)sys_removexattr(path, AD_EA_META); |
| 788 | @@ -812,6 +848,7 @@ EC_CLEANUP: |
| 789 | LOG(log_error, logtype_ad, "ad_header_read_ea(\"%s\"): deleted invalid metadata EA", fullpathname(path), nentries); |
| 790 | errno = ENOENT; |
| 791 | } |
| 792 | +#endif |
| 793 | EC_EXIT; |
| 794 | } |
| 795 | |