Version in base suite: 4.2.3~ds-1+deb13u1 Base version: netatalk_4.2.3~ds-1+deb13u1 Target version: netatalk_4.2.3~ds-1+deb13u2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/n/netatalk/netatalk_4.2.3~ds-1+deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/n/netatalk/netatalk_4.2.3~ds-1+deb13u2.dsc changelog | 11 patches/002_CVE_batch_2026-05.patch | 1297 ++++++++++++++++++++++++++++++++++++ patches/series | 1 3 files changed, 1309 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpzz_xhi8u/netatalk_4.2.3~ds-1+deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpzz_xhi8u/netatalk_4.2.3~ds-1+deb13u2.dsc: no acceptable signature found diff -Nru netatalk-4.2.3~ds/debian/changelog netatalk-4.2.3~ds/debian/changelog --- netatalk-4.2.3~ds/debian/changelog 2025-10-05 21:11:55.000000000 +0000 +++ netatalk-4.2.3~ds/debian/changelog 2026-05-16 14:41:29.000000000 +0000 @@ -1,3 +1,14 @@ +netatalk (4.2.3~ds-1+deb13u2) trixie-security; urgency=high + + [ Daniel Markstedt ] + * add patch that fixes: + CVE-2026-44047 CVE-2026-44048 CVE-2026-44049 CVE-2026-44050 CVE-2026-44051 + CVE-2026-44052 CVE-2026-44054 CVE-2026-44055 CVE-2026-44057 CVE-2026-44060 + CVE-2026-44062 CVE-2026-44064 CVE-2026-44066 CVE-2026-44068 CVE-2026-44076 + CVE-2026-45354 CVE-2026-45355 CVE-2026-45356 CVE-2026-45698 CVE-2026-45699 + + -- Daniel Markstedt Sat, 16 May 2026 16:41:29 +0200 + netatalk (4.2.3~ds-1+deb13u1) trixie; urgency=high [ Daniel Markstedt ] diff -Nru netatalk-4.2.3~ds/debian/patches/002_CVE_batch_2026-05.patch netatalk-4.2.3~ds/debian/patches/002_CVE_batch_2026-05.patch --- netatalk-4.2.3~ds/debian/patches/002_CVE_batch_2026-05.patch 1970-01-01 00:00:00.000000000 +0000 +++ netatalk-4.2.3~ds/debian/patches/002_CVE_batch_2026-05.patch 2026-05-16 14:41:29.000000000 +0000 @@ -0,0 +1,1297 @@ +Description: patch 20 CVEs that were disclosed on 2026-05-13 +CVE-2026-44047: cnid: protect against MySQL CNID filename SQL injection +CVE-2026-44048: libatalk: fix UCS-2 terminator bounds in charset conversion +CVE-2026-44049: libatalk: reserve charset terminator space in conversion +CVE-2026-44050: cnid_dbd: validate CNID request name length +CVE-2026-44051: afpd: validate symlink targets from FinderInfo +CVE-2026-44052: libatalk: avoid logging LDAP bind passwords +CVE-2026-44054: afpd: randomize reconnect session token +CVE-2026-44055: afpd: correct bitwise check and escape user in FCE notify script +CVE-2026-44057,CVE-2026-44066: afpd: fix spotlight unmarshalling depth and dead check +CVE-2026-44060: libatalk/dsi: fix write underflow in dsi_writeinit +CVE-2026-44062: libatalk/unicode: guard UCS2 slash and colon writes +CVE-2026-44064: libatalk/asp: bounds-check ASP session ID +CVE-2026-44068: libatalk/vfs: reject slash in EA names +CVE-2026-44076: netatalk: fix Spotlight volume path shell quoting +CVE-2026-45354: libatalk/dsi: guard cmdlen override to DSIWrite to prevent DoS +CVE-2026-45355: afpd: fix signed integer underflow in sl_unpack_cpx string length +CVE-2026-45356: afpd: guard against unsigned underflow in sl_unpack_loop count decrement +CVE-2026-45698,CVE-2026-45699: afpd: fix stack buffer overflow in copydir() and deletedir() +Author: Daniel Markstedt +Applied-Upstream: 4.4.3 +Last-Update: 2026-05-15 + +--- netatalk-4.2.3~ds.orig/etc/afpd/auth.c ++++ netatalk-4.2.3~ds/etc/afpd/auth.c +@@ -367,22 +367,20 @@ int afp_zzz(AFPObj *obj, char *ibuf, siz + /* ---------------------- */ + static int create_session_token(AFPObj *obj) + { +- pid_t pid; +- +- /* use 8 bytes for token as OSX, don't know if it helps */ +- if ( sizeof(pid_t) > SESSIONTOKEN_LEN) { +- LOG(log_error, logtype_afpd, "sizeof(pid_t) > %u", SESSIONTOKEN_LEN ); ++ if (NULL == (obj->sinfo.sessiontoken = malloc(SESSIONTOKEN_LEN))) { + return AFPERR_MISC; + } + +- if ( NULL == (obj->sinfo.sessiontoken = malloc(SESSIONTOKEN_LEN)) ) +- return AFPERR_MISC; +- +- memset(obj->sinfo.sessiontoken, 0, SESSIONTOKEN_LEN); + obj->sinfo.sessiontoken_len = SESSIONTOKEN_LEN; +- pid = getpid(); +- memcpy(obj->sinfo.sessiontoken, &pid, sizeof(pid_t)); + ++ if (uam_random_string(obj, obj->sinfo.sessiontoken, SESSIONTOKEN_LEN) < 0) { ++ free(obj->sinfo.sessiontoken); ++ obj->sinfo.sessiontoken = NULL; ++ return AFPERR_MISC; ++ } ++ ++ ipc_child_write(obj->ipc_fd, IPC_SESSIONTOKEN, SESSIONTOKEN_LEN, ++ obj->sinfo.sessiontoken); + return 0; + } + +@@ -509,8 +507,6 @@ int afp_disconnect(AFPObj *obj, char *ib + DSI *dsi = (DSI *)obj->dsi; + uint16_t type; + uint32_t tklen; +- pid_t token; +- int i; + + *rbuflen = 0; + ibuf += 2; +@@ -530,23 +526,9 @@ int afp_disconnect(AFPObj *obj, char *ib + tklen = ntohl(tklen); + ibuf += sizeof(tklen); + +- if ( sizeof(pid_t) > SESSIONTOKEN_LEN) { +- LOG(log_error, logtype_afpd, "sizeof(pid_t) > %u", SESSIONTOKEN_LEN ); +- return AFPERR_MISC; +- } + if (tklen != SESSIONTOKEN_LEN) { + return AFPERR_MISC; + } +- tklen = sizeof(pid_t); +- memcpy(&token, ibuf, tklen); +- +- /* our stuff is pid + zero pad */ +- ibuf += tklen; +- for (i = tklen; i < SESSIONTOKEN_LEN; i++, ibuf++) { +- if (*ibuf != 0) { +- return AFPERR_MISC; +- } +- } + + LOG(log_note, logtype_afpd, "afp_disconnect: trying primary reconnect"); + dsi->flags |= DSI_RECONINPROG; +@@ -556,7 +538,8 @@ int afp_disconnect(AFPObj *obj, char *ib + setitimer(ITIMER_REAL, &none, NULL); + + /* check for old session, possibly transferring session from here to there */ +- if (ipc_child_write(obj->ipc_fd, IPC_DISCOLDSESSION, tklen, &token) != 0) ++ if (ipc_child_write(obj->ipc_fd, IPC_DISCOLDSESSION, SESSIONTOKEN_LEN, ++ ibuf) != 0) + goto exit; + /* write uint16_t DSI request ID */ + if (writet(obj->ipc_fd, &dsi->header.dsi_requestID, 2, 0, 2) != 2) { +--- netatalk-4.2.3~ds.orig/etc/afpd/desktop.c ++++ netatalk-4.2.3~ds/etc/afpd/desktop.c +@@ -757,7 +757,6 @@ char *mtoupath(const struct vol *vol, ch + static char upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */ + char *m, *u; + size_t inplen; +- size_t outlen; + uint16_t flags; + + if ( *mpath == '\0' ) { +@@ -777,9 +776,11 @@ char *mtoupath(const struct vol *vol, ch + u = upath; + + inplen = strlen(m); +- outlen = MAXPATHLEN; + +- if ((size_t)-1 == (outlen = convert_charset ( utf8?CH_UTF8_MAC:vol->v_maccharset, vol->v_volcharset, vol->v_maccharset, m, inplen, u, outlen, &flags)) ) { ++ if ((size_t)-1 == convert_charset(utf8 ? CH_UTF8_MAC : ++ vol->v_maccharset, vol->v_volcharset, vol->v_maccharset, m, inplen, u, ++ sizeof(upath), ++ &flags)) { + LOG(log_error, logtype_afpd, "conversion from %s to %s for %s failed.", (utf8)?"UTF8-MAC":vol->v_maccodepage, vol->v_volcodepage, mpath); + return NULL; + } +@@ -806,7 +807,7 @@ char *utompath(const struct vol *vol, ch + u = upath; + + /* convert charsets */ +- if ((size_t)-1 == ( outlen = convert_charset ( vol->v_volcharset, utf8?CH_UTF8_MAC:vol->v_maccharset, vol->v_maccharset, u, outlen, mpath, MAXPATHLEN, &flags)) ) { ++ if ((size_t)-1 == ( outlen = convert_charset ( vol->v_volcharset, utf8?CH_UTF8_MAC:vol->v_maccharset, vol->v_maccharset, u, outlen, mpath, sizeof(mpath), &flags)) ) { + LOG(log_error, logtype_afpd, "Conversion from %s to %s for %s (%u) failed.", vol->v_volcodepage, vol->v_maccodepage, u, ntohl(id)); + goto utompath_error; + } +--- netatalk-4.2.3~ds.orig/etc/afpd/directory.c ++++ netatalk-4.2.3~ds/etc/afpd/directory.c +@@ -154,7 +154,7 @@ static int deletedir(const struct vol *v + + snprintf(path, MAXPATHLEN, "%s/", dir); + len++; +- remain = strlen(path) -len -1; ++ remain = sizeof(path) - len - 1; + while ((de = readdir(dp)) && err == AFP_OK) { + /* skip this and previous directory */ + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) +@@ -211,11 +211,11 @@ static int copydir(struct vol *vol, stru + /* set things up to copy */ + snprintf(spath, MAXPATHLEN, "%s/", src); + slen++; +- srem = strlen(spath) - slen -1; ++ srem = sizeof(spath) - slen - 1; + + snprintf(dpath, MAXPATHLEN, "%s/", dst); + dlen++; +- drem = strlen(dpath) - dlen -1; ++ drem = sizeof(dpath) - dlen - 1; + + err = AFP_OK; + while ((de = readdir(dp))) { +--- netatalk-4.2.3~ds.orig/etc/afpd/fce_api.c ++++ netatalk-4.2.3~ds/etc/afpd/fce_api.c +@@ -384,8 +384,13 @@ static void send_fce_event(const AFPObj + } + if (fce_ev_info | FCE_EV_INFO_PID) + bformata(cmd, " -p %" PRIu64 "", (uint64_t)getpid()); +- if (fce_ev_info | FCE_EV_INFO_USER) +- bformata(cmd, " -u %s", user); ++ if (fce_ev_info & FCE_EV_INFO_USER) { ++ bstring buser = bfromcstr(user); ++ bfindreplace(buser, slash, slashrep, 0); ++ bfindreplace(buser, quote, quoterep, 0); ++ bformata(cmd, " -u '%s'", bdata(buser)); ++ bdestroy(buser); ++ } + if (oldpath) { + bstring boldpath = bfromcstr(oldpath); + bfindreplace(boldpath, slash, slashrep, 0); +--- netatalk-4.2.3~ds.orig/etc/afpd/file.c ++++ netatalk-4.2.3~ds/etc/afpd/file.c +@@ -70,6 +70,83 @@ static const uint8_t old_ufinderi[] = { + 'T', 'E', 'X', 'T', 'U', 'N', 'I', 'X' + }; + ++static int has_dotdot_component(const char *path) ++{ ++ const char *p = path; ++ ++ while (*p) { ++ size_t len; ++ const char *slash = strchr(p, '/'); ++ len = slash ? (size_t)(slash - p) : strlen(p); ++ ++ if (len == 2 && p[0] == '.' && p[1] == '.') { ++ return 1; ++ } ++ ++ if (!slash) { ++ break; ++ } ++ ++ p = slash + 1; ++ } ++ ++ return 0; ++} ++ ++static int path_is_inside_volume(const struct vol *vol, const char *path) ++{ ++ size_t volpath_len = strlen(vol->v_path); ++ ++ if (strncmp(path, vol->v_path, volpath_len) != 0) { ++ return 0; ++ } ++ ++ return path[volpath_len] == '\0' || path[volpath_len] == '/'; ++} ++ ++static int symlink_target_safe(const struct vol *vol, ++ const char *link_path, ++ const char *target) ++{ ++ char target_path[MAXPATHLEN + 1]; ++ char link_dir[MAXPATHLEN + 1]; ++ char *slash; ++ char *resolved_target = NULL; ++ int safe = 0; ++ ++ if (target[0] == '/' || has_dotdot_component(target)) { ++ return 0; ++ } ++ ++ if (strlcpy(link_dir, link_path, sizeof(link_dir)) >= sizeof(link_dir)) { ++ return 0; ++ } ++ ++ slash = strrchr(link_dir, '/'); ++ ++ if (slash == link_dir) { ++ slash[1] = '\0'; ++ } else if (slash) { ++ *slash = '\0'; ++ } else { ++ strlcpy(link_dir, ".", sizeof(link_dir)); ++ } ++ ++ if (snprintf(target_path, sizeof(target_path), "%s/%s", link_dir, target) ++ >= sizeof(target_path)) { ++ return 0; ++ } ++ ++ resolved_target = realpath_safe(target_path); ++ ++ if (resolved_target) { ++ safe = path_is_inside_volume(vol, resolved_target); ++ free(resolved_target); ++ } ++ ++ return safe; ++} ++ + /* ---------------------- + */ + static int default_type(void *finder) +@@ -924,6 +1001,12 @@ int setfilparams(const AFPObj *obj, stru + goto setfilparam_done; + } + symbuf[len] = 0; ++ ++ if (!symlink_target_safe(vol, path->u_name, symbuf)) { ++ err = AFPERR_ACCESS; ++ goto setfilparam_done; ++ } ++ + if (symlink(symbuf, path->u_name) != 0) { + err = AFPERR_MISC; + goto setfilparam_done; +--- netatalk-4.2.3~ds.orig/etc/afpd/mangle.c ++++ netatalk-4.2.3~ds/etc/afpd/mangle.c +@@ -33,7 +33,7 @@ static size_t mangle_extension(const str + uint16_t flags = CONV_FORCE | CONV_UNESCAPEHEX; + size_t len = convert_charset(vol->v_volcharset, charset, + vol->v_maccharset, p, strlen(p), +- extension, MAX_EXT_LENGTH, &flags); ++ extension, MAX_EXT_LENGTH + 2, &flags); + + if (len != (size_t)-1) return len; + } +@@ -73,7 +73,7 @@ static char *demangle_checks(const struc + + flags = CONV_IGNORE | CONV_UNESCAPEHEX; + if ( (size_t) -1 == (len = convert_charset(vol->v_volcharset, vol->v_maccharset, 0, +- uname, strlen(uname), buffer, MAXPATHLEN, &flags)) ) { ++ uname, strlen(uname), buffer, sizeof(buffer), &flags)) ) { + return mfilename; + } + /* If the filename is too long we also needed to mangle */ +@@ -94,7 +94,7 @@ static char *demangle_checks(const struc + if (len) { + /* convert the buffer to UTF8_MAC ... */ + if ((size_t) -1 == (len = convert_charset(vol->v_maccharset, CH_UTF8_MAC, 0, +- buffer, len, buffer, MAXPATHLEN, &flags)) ) { ++ buffer, len, buffer, sizeof(buffer), &flags)) ) { + return mfilename; + } + /* Now compare the two names, they have to match the number of characters in buffer */ +@@ -117,7 +117,7 @@ static char *demangle_checks(const struc + /* characters have to match ... again a possible race FIXME */ + + if ( (size_t) -1 == (len = convert_charset(vol->v_volcharset, CH_UTF8_MAC, 0, +- uname, strlen(uname), buffer, MAXPATHLEN, &flags)) ) { ++ uname, strlen(uname), buffer, sizeof(buffer), &flags)) ) { + return mfilename; + } + +@@ -243,7 +243,7 @@ demangle_osx(const struct vol *vol, char + char * + mangle(const struct vol *vol, char *filename, size_t filenamelen, char *uname, cnid_t id, int flags) { + char *m = NULL; +- static char mfilename[MAXPATHLEN]; /* way > maxlen */ ++ static char mfilename[MAXPATHLEN + 2]; /* way > maxlen */ + char mangle_suffix[MANGLE_LENGTH + 1]; + char ext[MAX_EXT_LENGTH +2]; /* for convert_charset dest_len parameter +2 */ + size_t ext_len; +@@ -267,10 +267,11 @@ mangle(const struct vol *vol, char *file + + if (filenamelen + k + ext_len > maxlen) { + uint16_t opt = CONV_FORCE | CONV_UNESCAPEHEX; ++ size_t prefix_len = maxlen - k - ext_len; + size_t n = convert_charset(vol->v_volcharset, + (flags & 2) ? CH_UTF8_MAC : vol->v_maccharset, + vol->v_maccharset, uname, strlen(uname), +- m, maxlen - k - ext_len, &opt); ++ m, prefix_len + 2, &opt); + m[n != (size_t)-1 ? n : 0] = 0; + } else { + strlcpy(m, filename, filenamelen + 1); +--- netatalk-4.2.3~ds.orig/etc/afpd/spotlight_marshalling.c ++++ netatalk-4.2.3~ds/etc/afpd/spotlight_marshalling.c +@@ -68,7 +68,13 @@ + + /* Forward declarations */ + static int sl_pack_loop(DALLOC_CTX *query, char *buf, int offset, char *toc_buf, int *toc_idx); +-static int sl_unpack_loop(DALLOC_CTX *query, const char *buf, int offset, uint count, const uint toc_offset, const uint encoding); ++static int sl_unpack_loop(DALLOC_CTX *query, const char *buf, int offset, ++ uint count, const uint toc_offset, const uint encoding, ++ int depth); ++static int sl_unpack_cpx(DALLOC_CTX *query, const char *buf, const int offset, ++ uint cpx_query_type, uint cpx_query_count, ++ const uint toc_offset, const uint encoding, int depth); ++static int sl_unpack_r(DALLOC_CTX *query, const char *buf, int depth); + + /************************************************************************************************** + * Wrapper functions for the *VAL macros with bound checking +@@ -592,10 +598,15 @@ static int sl_unpack_cpx(DALLOC_CTX *que + uint cpx_query_type, + uint cpx_query_count, + const uint toc_offset, +- const uint encoding) ++ const uint encoding, ++ int depth) + { + EC_INIT; + ++ if (depth >= SUBQ_SAFETY_LIM) { ++ EC_FAIL; ++ } ++ + int roffset = offset; + uint64_t query_data64; + uint unicode_encoding; +@@ -609,13 +620,13 @@ static int sl_unpack_cpx(DALLOC_CTX *que + switch (cpx_query_type) { + case SQ_CPX_TYPE_ARRAY: + sl_array = talloc_zero(query, sl_array_t); +- EC_NEG1_LOG( roffset = sl_unpack_loop(sl_array, buf, offset, cpx_query_count, toc_offset, encoding) ); ++ EC_NEG1_LOG( roffset = sl_unpack_loop(sl_array, buf, offset, cpx_query_count, toc_offset, encoding, depth + 1) ); + dalloc_add(query, sl_array, sl_array_t); + break; + + case SQ_CPX_TYPE_DICT: + sl_dict = talloc_zero(query, sl_dict_t); +- EC_NEG1_LOG( roffset = sl_unpack_loop(sl_dict, buf, offset, cpx_query_count, toc_offset, encoding) ); ++ EC_NEG1_LOG( roffset = sl_unpack_loop(sl_dict, buf, offset, cpx_query_count, toc_offset, encoding, depth + 1) ); + dalloc_add(query, sl_dict, sl_dict_t); + break; + +@@ -627,6 +638,10 @@ static int sl_unpack_cpx(DALLOC_CTX *que + slen = qlen - 16 + used_in_last_block; + + if (cpx_query_type == SQ_CPX_TYPE_STRING) { ++ if (slen < 0 || offset + 8 + slen > (int)toc_offset) { ++ EC_FAIL; ++ } ++ + p = dalloc_strndup(query, buf + offset + 8, slen); + } else { + unicode_encoding = spotlight_get_utf16_string_encoding(buf, offset + 8, slen, encoding); +@@ -634,12 +649,18 @@ static int sl_unpack_cpx(DALLOC_CTX *que + if (unicode_encoding & SL_ENC_BIG_ENDIAN) + EC_FAIL_LOG("Unsupported big endian UTF16 string"); + slen -= mark_exists ? 2 : 0; +- EC_NEG1( convert_string_allocate(CH_UCS2, +- CH_UTF8, +- buf + offset + (mark_exists ? 10 : 8), +- slen, +- &tmp) ); +- p = dalloc_strndup(query, tmp, strlen(tmp)); ++ ++ if (slen <= 0 || offset + (mark_exists ? 10 : 8) + slen > (int)toc_offset) { ++ EC_FAIL; ++ } ++ ++ size_t tmp_len; ++ EC_NEG1(tmp_len = convert_string_allocate(CH_UCS2, ++ CH_UTF8, ++ buf + offset + (mark_exists ? 10 : 8), ++ slen, ++ &tmp)); ++ p = dalloc_strndup(query, tmp, tmp_len); + free(tmp); + } + +@@ -654,7 +675,7 @@ static int sl_unpack_cpx(DALLOC_CTX *que + EC_FAIL_LOG("SQ_CPX_TYPE_FILEMETA: query_length <= 8: %d", qlen); + } else { + sl_fm = talloc_zero(query, sl_filemeta_t); +- EC_NEG1_LOG( sl_unpack(sl_fm, buf + offset + 8) ); ++ EC_NEG1_LOG( sl_unpack_r(sl_fm, buf + offset + 8, depth + 1) ); + dalloc_add(query, sl_fm, sl_filemeta_t); + } + roffset += qlen; +@@ -682,9 +703,15 @@ static int sl_unpack_loop(DALLOC_CTX *qu + int offset, + uint count, + const uint toc_offset, +- const uint encoding) ++ const uint encoding, ++ int depth) + { + EC_INIT; ++ ++ if (depth >= SUBQ_SAFETY_LIM) { ++ EC_FAIL; ++ } ++ + int i, toc_index, query_length; + uint subcount; + uint64_t query_data64, query_type; +@@ -706,7 +733,7 @@ static int sl_unpack_loop(DALLOC_CTX *qu + cpx_query_type = (query_data64 & 0xffff0000) >> 16; + cpx_query_count = query_data64 >> 32; + +- EC_NEG1_LOG( offset = sl_unpack_cpx(query, buf, offset + 8, cpx_query_type, cpx_query_count, toc_offset, encoding)); ++ EC_NEG1_LOG( offset = sl_unpack_cpx(query, buf, offset + 8, cpx_query_type, cpx_query_count, toc_offset, encoding, depth + 1)); + count--; + break; + case SQ_TYPE_NULL: +@@ -716,6 +743,11 @@ static int sl_unpack_loop(DALLOC_CTX *qu + nil = 0; + for (i = 0; i < subcount; i++) + dalloc_add_copy(query, &nil, sl_nil_t); ++ ++ if (subcount > count) { ++ EC_FAIL; ++ } ++ + offset += query_length; + count -= subcount; + break; +@@ -727,21 +759,41 @@ static int sl_unpack_loop(DALLOC_CTX *qu + break; + case SQ_TYPE_INT64: + EC_NEG1_LOG( subcount = sl_unpack_ints(query, buf, offset, encoding) ); ++ ++ if (subcount > count) { ++ EC_FAIL; ++ } ++ + offset += query_length; + count -= subcount; + break; + case SQ_TYPE_UUID: + EC_NEG1_LOG( subcount = sl_unpack_uuid(query, buf, offset, encoding) ); ++ ++ if (subcount > count) { ++ EC_FAIL; ++ } ++ + offset += query_length; + count -= subcount; + break; + case SQ_TYPE_FLOAT: + EC_NEG1_LOG( subcount = sl_unpack_floats(query, buf, offset, encoding) ); ++ ++ if (subcount > count) { ++ EC_FAIL; ++ } ++ + offset += query_length; + count -= subcount; + break; + case SQ_TYPE_DATE: + EC_NEG1_LOG( subcount = sl_unpack_date(query, buf, offset, encoding) ); ++ ++ if (subcount > count) { ++ EC_FAIL; ++ } ++ + offset += query_length; + count -= subcount; + break; +@@ -785,10 +837,10 @@ EC_CLEANUP: + return len; + } + +-int sl_unpack(DALLOC_CTX *query, const char *buf) ++static int sl_unpack_r(DALLOC_CTX *query, const char *buf, int depth) + { + EC_INIT; +- int encoding, toc_entries; ++ int encoding; + uint64_t toc_offset; + + if (strncmp(buf, "md031234", 8) == 0) +@@ -799,16 +851,19 @@ int sl_unpack(DALLOC_CTX *query, const c + buf += 8; + + toc_offset = ((sl_unpack_uint64(buf, 0, encoding) >> 32) - 1 ) * 8; +- if (toc_offset < 0 || (toc_offset > 65000)) { ++ if (toc_offset > 65000) { + EC_FAIL; + } + + buf += 8; + +- toc_entries = (int)(sl_unpack_uint64(buf, toc_offset, encoding) & 0xffff); +- +- EC_NEG1( sl_unpack_loop(query, buf, 0, 1, toc_offset + 8, encoding) ); ++ EC_NEG1( sl_unpack_loop(query, buf, 0, 1, toc_offset + 8, encoding, depth) ); + + EC_CLEANUP: + EC_EXIT; + } ++ ++int sl_unpack(DALLOC_CTX *query, const char *buf) ++{ ++ return sl_unpack_r(query, buf, 0); ++} +--- netatalk-4.2.3~ds.orig/etc/cnid_dbd/comm.c ++++ netatalk-4.2.3~ds/etc/cnid_dbd/comm.c +@@ -220,6 +220,14 @@ int comm_rcv(struct cnid_dbd_rqst *rqst, + return 0; + } + rqst->name = nametmp; ++ ++ if (rqst->namelen > MAXPATHLEN) { ++ LOG(log_error, logtype_cnid, "comm_rcv: name too long: %zu", ++ rqst->namelen); ++ invalidate_fd(cur_fd); ++ return 0; ++ } ++ + if (rqst->namelen && readt(cur_fd, (char *)rqst->name, rqst->namelen, 1, CNID_DBD_TIMEOUT) + != rqst->namelen) { + LOG(log_error, logtype_cnid, "error reading message name: %s", strerror(errno)); +--- netatalk-4.2.3~ds.orig/etc/netatalk/netatalk.c ++++ netatalk-4.2.3~ds/etc/netatalk/netatalk.c +@@ -80,27 +80,125 @@ static bool service_running(pid_t pid) + return false; + } + ++static bstring quote_gvariant_string(const char *str) ++{ ++ bstring quoted = bfromcstr("\""); ++ ++ if (quoted == NULL) { ++ return NULL; ++ } ++ ++ for (const unsigned char *p = (const unsigned char *)str; *p; p++) { ++ int rc; ++ ++ switch (*p) { ++ case '\\': ++ rc = bcatcstr(quoted, "\\\\"); ++ break; ++ ++ case '"': ++ rc = bcatcstr(quoted, "\\\""); ++ break; ++ ++ case '\n': ++ rc = bcatcstr(quoted, "\\n"); ++ break; ++ ++ case '\r': ++ rc = bcatcstr(quoted, "\\r"); ++ break; ++ ++ case '\t': ++ rc = bcatcstr(quoted, "\\t"); ++ break; ++ ++ default: ++ rc = bconchar(quoted, (char) * p); ++ break; ++ } ++ ++ if (rc != BSTR_OK) { ++ bdestroy(quoted); ++ return NULL; ++ } ++ } ++ ++ if (bconchar(quoted, '"') != BSTR_OK) { ++ bdestroy(quoted); ++ return NULL; ++ } ++ ++ return quoted; ++} ++ ++static bstring quote_shell_arg(const char *str) ++{ ++ bstring quoted = bfromcstr("'"); ++ ++ if (quoted == NULL) { ++ return NULL; ++ } ++ ++ for (const char *p = str; *p; p++) { ++ int rc; ++ ++ if (*p == '\'') { ++ rc = bcatcstr(quoted, "'\\''"); ++ } else { ++ rc = bconchar(quoted, *p); ++ } ++ ++ if (rc != BSTR_OK) { ++ bdestroy(quoted); ++ return NULL; ++ } ++ } ++ ++ if (bconchar(quoted, '\'') != BSTR_OK) { ++ bdestroy(quoted); ++ return NULL; ++ } ++ ++ return quoted; ++} ++ + /* Set Tracker Miners to index all our volumes */ + static int set_sl_volumes(void) + { + EC_INIT; + const struct vol *volumes, *vol; +- struct bstrList *vollist = bstrListCreate(); +- bstring sep = bfromcstr(", "); +- bstring volnamelist = NULL, cmd = NULL; +- ++ struct bstrList *vollist = NULL; ++ bstring sep = NULL, volnamelist = NULL, gvariant = NULL, shellarg = NULL; ++ bstring cmd = NULL; ++ EC_NULL_LOG(vollist = bstrListCreate()); ++ EC_NULL_LOG(sep = bfromcstr(", ")); + EC_NULL_LOG( volumes = getvolumes() ); + + for (vol = volumes; vol; vol = vol->v_next) { + if (vol->v_flags & AFPVOL_SPOTLIGHT) { +- bstring volnamequot = bformat("'%s'", vol->v_path); +- bstrListPush(vollist, volnamequot); ++ bstring volnamequot = quote_gvariant_string(vol->v_path); ++ EC_NULL_LOG(volnamequot); ++ ++ if (bstrListPush(vollist, volnamequot) != BSTR_OK) { ++ LOG(log_error, logtype_default, ++ "set_sl_volumes: failed to initialize indexing for %s", ++ bdata(volnamequot)); ++ bdestroy(volnamequot); ++ EC_FAIL; ++ } + } + } + + volnamelist = bjoin(vollist, sep); +- cmd = bformat("gsettings set org.freedesktop.Tracker.Miner.Files index-recursive-directories \"[%s]\"", +- bdata(volnamelist) ? bdata(volnamelist) : ""); ++ EC_NULL_LOG(volnamelist); ++ gvariant = bformat("[%s]", bdata(volnamelist)); ++ EC_NULL_LOG(gvariant); ++ shellarg = quote_shell_arg(bdata(gvariant)); ++ EC_NULL_LOG(shellarg); ++ cmd = bformat("gsettings set org.freedesktop.Tracker.Miner.Files" ++ " index-recursive-directories %s", ++ bdata(shellarg)); ++ EC_NULL_LOG(cmd); + LOG(log_debug, logtype_sl, "set_sl_volumes: %s", bdata(cmd)); + system(bdata(cmd)); + +@@ -112,6 +210,12 @@ EC_CLEANUP: + bdestroy(cmd); + if (sep) + bdestroy(sep); ++ if (gvariant) { ++ bdestroy(gvariant); ++ } ++ if (shellarg) { ++ bdestroy(shellarg); ++ } + if (vollist) + bstrListDestroy(vollist); + if (volnamelist) +--- netatalk-4.2.3~ds.orig/include/atalk/server_child.h ++++ netatalk-4.2.3~ds/include/atalk/server_child.h +@@ -28,6 +28,8 @@ typedef struct afp_child { + int afpch_ipc_fd; /* socket for IPC bw afpd parent and childs */ + int16_t afpch_state; /* state of AFP session (eg active, sleeping, disconnected) */ + char *afpch_volumes; /* mounted volumes */ ++ char *afpch_sessiontoken; /*!< session token for reconnect */ ++ uint32_t afpch_sessiontoken_len; + struct afp_child **afpch_prevp; + struct afp_child *afpch_next; + } afp_child_t; +@@ -50,7 +52,10 @@ extern afp_child_t *server_child_resolve + extern void server_child_kill(server_child_t *, int); + extern void server_child_kill_one_by_id(server_child_t *children, pid_t pid, uid_t, + uint32_t len, char *id, uint32_t boottime); +-extern int server_child_transfer_session(server_child_t *children, pid_t, uid_t, int, uint16_t); ++extern int server_child_transfer_session(server_child_t *children, ++ const char *token, uint32_t tokenlen, uid_t, int, uint16_t); ++extern void server_child_set_token(server_child_t *children, pid_t pid, ++ const char *token, uint32_t tokenlen); + extern void server_child_handler(server_child_t *); + extern void server_child_login_done(server_child_t *children, pid_t pid, uid_t); + extern void server_reset_signal(void); +--- netatalk-4.2.3~ds.orig/include/atalk/server_ipc.h ++++ netatalk-4.2.3~ds/include/atalk/server_ipc.h +@@ -10,6 +10,7 @@ + #define IPC_STATE 2 /* pass AFP session state */ + #define IPC_VOLUMES 3 /* pass list of open volumes */ + #define IPC_LOGINDONE 4 ++#define IPC_SESSIONTOKEN 5 /*!< pass session token to parent for reconnect lookup */ + + extern int ipc_server_read(server_child_t *children, int fd); + extern int ipc_child_write(int fd, uint16_t command, int len, void *token); +--- netatalk-4.2.3~ds.orig/libatalk/acl/ldap.c ++++ netatalk-4.2.3~ds/libatalk/acl/ldap.c +@@ -178,8 +178,8 @@ retry: + } else if (LDAP_AUTH_SIMPLE == ldap_auth_method) { + if (ldap_bind_s(ld, ldap_auth_dn, ldap_auth_pw, ldap_auth_method) != LDAP_SUCCESS ) { + LOG(log_error, logtype_default, +- "ldap: ldap_bind failed: ldap_auth_dn: \'%s\', ldap_auth_pw: \'%s\', ldap_auth_method: \'%d\'", +- ldap_auth_dn, ldap_auth_pw, ldap_auth_method); ++ "ldap: ldap_bind failed: ldap_auth_dn: \'%s\', ldap_auth_method: \'%d\'", ++ ldap_auth_dn, ldap_auth_method); + free(ld); + ld = NULL; + return -1; +--- netatalk-4.2.3~ds.orig/libatalk/asp/asp_getsess.c ++++ netatalk-4.2.3~ds/libatalk/asp/asp_getsess.c +@@ -170,7 +170,12 @@ ASP asp_getsession(ASP asp, server_child + + switch ( asp->cmdbuf[ 0 ] ) { + case ASPFUNC_TICKLE: +- sid = asp->cmdbuf[1]; ++ sid = (unsigned char)asp->cmdbuf[1]; ++ ++ if (sid >= children->servch_nsessions) { ++ break; ++ } ++ + if ((asp_ac[sid] != NULL) && (asp_ac[sid]->ac_state != ACSTATE_DEAD)) + asp_ac[sid]->ac_state = ACSTATE_OK; + break; +--- netatalk-4.2.3~ds.orig/libatalk/cnid/mysql/cnid_mysql.c ++++ netatalk-4.2.3~ds/libatalk/cnid/mysql/cnid_mysql.c +@@ -320,14 +320,36 @@ int cnid_mysql_update(struct _cnid_db *c + uint64_t dev = st->st_dev; + uint64_t ino = st->st_ino; + ++ MYSQL_STMT *delete_stmt = NULL; ++ + do { ++ MYSQL_BIND delete_param[2]; ++ unsigned long delete_name_len = len; ++ uint64_t delete_did = ntohl(did); ++ char *delete_sql = NULL; ++ + EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con, + "DELETE FROM `%s` WHERE Id=%" PRIu32, + db->cnid_mysql_voluuid_str, + ntohl(id)) ); +- EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con, +- "DELETE FROM `%s` WHERE Did=%" PRIu32 " AND Name='%s'", +- db->cnid_mysql_voluuid_str, ntohl(did), name) ); ++ EC_NULL(delete_stmt = mysql_stmt_init(db->cnid_mysql_con)); ++ EC_NEG1(asprintf(&delete_sql, "DELETE FROM `%s` WHERE Did=? AND Name=?", ++ db->cnid_mysql_voluuid_str)); ++ EC_ZERO_LOG(mysql_stmt_prepare(delete_stmt, delete_sql, strlen(delete_sql))); ++ free(delete_sql); ++ delete_sql = NULL; ++ memset(delete_param, 0, sizeof(delete_param)); ++ delete_param[0].buffer_type = MYSQL_TYPE_LONGLONG; ++ delete_param[0].buffer = &delete_did; ++ delete_param[0].is_unsigned = true; ++ delete_param[1].buffer_type = MYSQL_TYPE_STRING; ++ delete_param[1].buffer = (char *)name; ++ delete_param[1].buffer_length = len; ++ delete_param[1].length = &delete_name_len; ++ EC_ZERO_LOG(mysql_stmt_bind_param(delete_stmt, delete_param)); ++ EC_ZERO_LOG(mysql_stmt_execute(delete_stmt)); ++ mysql_stmt_close(delete_stmt); ++ delete_stmt = NULL; + EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con, + "DELETE FROM `%s` WHERE DevNo=%" PRIu64 " AND InodeNo=%" PRIu64, + db->cnid_mysql_voluuid_str, dev, ino) ); +@@ -360,6 +382,9 @@ int cnid_mysql_update(struct _cnid_db *c + } while (update_id != ntohl(id)); + + EC_CLEANUP: ++ if (delete_stmt) { ++ mysql_stmt_close(delete_stmt); ++ } + EC_EXIT; + } + +@@ -614,8 +639,14 @@ cnid_t cnid_mysql_get(struct _cnid_db *c + EC_INIT; + CNID_mysql_private *db; + cnid_t id = CNID_INVALID; +- MYSQL_RES *result = NULL; +- MYSQL_ROW row; ++ MYSQL_STMT *stmt = NULL; ++ MYSQL_BIND param[2]; ++ MYSQL_BIND result[1]; ++ unsigned long name_len = len; ++ uint64_t did_param = ntohl(did); ++ unsigned long long result_id = 0; ++ bool have_result = false; ++ char *sql = NULL; + + if (!cdb || !(db = cdb->cnid_db_private) || !name) { + LOG(log_error, logtype_cnid, "cnid_mysql_get: Parameter error"); +@@ -630,31 +661,51 @@ cnid_t cnid_mysql_get(struct _cnid_db *c + } + + LOG(log_debug, logtype_cnid, "cnid_mysql_get(did: %" PRIu32 ", name: \"%s\"): START", +- ntohl(did),name); +- +- EC_NEG1( cnid_mysql_execute(db->cnid_mysql_con, +- "SELECT Id FROM `%s` " +- "WHERE Name='%s' AND Did=%" PRIu32, +- db->cnid_mysql_voluuid_str, +- name, +- ntohl(did)) ); ++ ntohl(did), name); + +- if ((result = mysql_store_result(db->cnid_mysql_con)) == NULL) { +- LOG(log_error, logtype_cnid, "MySQL query error: %s", mysql_error(db->cnid_mysql_con)); +- errno = CNID_ERR_DB; +- EC_FAIL; +- } ++ EC_NULL(stmt = mysql_stmt_init(db->cnid_mysql_con)); ++ EC_NEG1(asprintf(&sql, "SELECT Id FROM `%s` WHERE Name=? AND Did=?", ++ db->cnid_mysql_voluuid_str)); ++ EC_ZERO_LOG(mysql_stmt_prepare(stmt, sql, strlen(sql))); ++ free(sql); ++ sql = NULL; ++ memset(param, 0, sizeof(param)); ++ param[0].buffer_type = MYSQL_TYPE_STRING; ++ param[0].buffer = (char *)name; ++ param[0].buffer_length = len; ++ param[0].length = &name_len; ++ param[1].buffer_type = MYSQL_TYPE_LONGLONG; ++ param[1].buffer = &did_param; ++ param[1].is_unsigned = true; ++ EC_ZERO_LOG(mysql_stmt_bind_param(stmt, param)); ++ memset(result, 0, sizeof(result)); ++ result[0].buffer_type = MYSQL_TYPE_LONGLONG; ++ result[0].buffer = &result_id; ++ result[0].is_unsigned = true; ++ EC_ZERO_LOG(mysql_stmt_bind_result(stmt, result)); ++ EC_ZERO_LOG(mysql_stmt_execute(stmt)); ++ EC_ZERO_LOG(mysql_stmt_store_result(stmt)); ++ have_result = true; + +- if (mysql_num_rows(result)) { +- row = mysql_fetch_row(result); +- id = htonl(atoi(row[0])); ++ if (mysql_stmt_num_rows(stmt)) { ++ EC_ZERO(mysql_stmt_fetch(stmt)); ++ id = htonl((uint32_t)result_id); + } + + EC_CLEANUP: + LOG(log_debug, logtype_cnid, "cnid_mysql_get: id: %" PRIu32, ntohl(id)); + +- if (result) +- mysql_free_result(result); ++ if (have_result) { ++ mysql_stmt_free_result(stmt); ++ } ++ ++ if (stmt) { ++ mysql_stmt_close(stmt); ++ } ++ ++ if (sql) { ++ free(sql); ++ } + + return id; + } +--- netatalk-4.2.3~ds.orig/libatalk/dsi/dsi_stream.c ++++ netatalk-4.2.3~ds/libatalk/dsi/dsi_stream.c +@@ -631,7 +631,8 @@ int dsi_stream_receive(DSI *dsi) + dsi->header.dsi_data.dsi_doff = 12; + + /* Receiving DSIWrite data is done in AFP function, not here */ +- if (dsi->header.dsi_data.dsi_doff) { ++ if (dsi->header.dsi_command == DSIFUNC_WRITE ++ && dsi->header.dsi_data.dsi_doff) { + LOG(log_maxdebug, logtype_dsi, "dsi_stream_receive: write request"); + dsi->cmdlen = dsi->header.dsi_data.dsi_doff; + } +--- netatalk-4.2.3~ds.orig/libatalk/dsi/dsi_write.c ++++ netatalk-4.2.3~ds/libatalk/dsi/dsi_write.c +@@ -26,6 +26,11 @@ + size_t dsi_writeinit(DSI *dsi, void *buf, const size_t buflen) + { + size_t bytes = 0; ++ ++ if (ntohl(dsi->header.dsi_len) < dsi->header.dsi_data.dsi_doff) { ++ return 0; ++ } ++ + dsi->datasize = ntohl(dsi->header.dsi_len) - dsi->header.dsi_data.dsi_doff; + + if (dsi->eof > dsi->start) { +--- netatalk-4.2.3~ds.orig/libatalk/unicode/charcnv.c ++++ netatalk-4.2.3~ds/libatalk/unicode/charcnv.c +@@ -830,6 +830,11 @@ static size_t pull_charset_flags (charse + i_len--; + } else if (to_set == CH_UTF8_MAC || to_set == CH_MAC) { + /* convert to a '/' */ ++ if (o_len < 2) { ++ errno = E2BIG; ++ goto end; ++ } ++ + ucs2_t slash = 0x002f; + memcpy(outbuf, &slash, sizeof(ucs2_t)); + outbuf += 2; +@@ -838,6 +843,11 @@ static size_t pull_charset_flags (charse + i_len--; + } else { + /* keep as ':' */ ++ if (o_len < 2) { ++ errno = E2BIG; ++ goto end; ++ } ++ + ucs2_t ucs2 = 0x003a; + memcpy(outbuf, &ucs2, sizeof(ucs2_t)); + outbuf += 2; +@@ -865,6 +875,11 @@ static size_t pull_charset_flags (charse + } else if ((from_set == CH_UTF8_MAC || from_set == CH_MAC) + && (to_set != CH_UTF8_MAC || to_set != CH_MAC)) { + /* convert to ':' */ ++ if (o_len < 2) { ++ errno = E2BIG; ++ goto end; ++ } ++ + ucs2_t ucs2 = 0x003a; + memcpy(outbuf, &ucs2, sizeof(ucs2_t)); + outbuf += 2; +@@ -873,6 +888,11 @@ static size_t pull_charset_flags (charse + i_len--; + } else { + /* keep as '/' */ ++ if (o_len < 2) { ++ errno = E2BIG; ++ goto end; ++ } ++ + ucs2_t ucs2 = 0x002f; + memcpy(outbuf, &ucs2, sizeof(ucs2_t)); + outbuf += 2; +@@ -980,9 +1000,9 @@ end: + return (i_len + j == 0 || (option & CONV_FORCE)) ? destlen - o_len : (size_t)-1; + } + +-/* +- * FIXME the size is a mess we really need a malloc/free logic +- *`dest size must be dest_len +2 ++/*! ++ * @bug the size is a mess we really need a malloc/free logic ++ * @note dest_len must include space for the two-byte terminator + */ + size_t convert_charset ( charset_t from_set, charset_t to_set, charset_t cap_charset, const char *src, size_t src_len, char *dest, size_t dest_len, uint16_t *flags) + { +@@ -1020,8 +1040,17 @@ size_t convert_charset ( charset_t from_ + i_len = o_len; + } + /* null terminate */ +- u[i_len] = 0; +- u[i_len +1] = 0; ++ { ++ size_t u_size = (u == buffer2) ? sizeof(buffer2) : sizeof(buffer); ++ ++ if (i_len > u_size - 2) { ++ errno = E2BIG; ++ return (size_t) -1; ++ } ++ ++ ((char *)u)[i_len] = 0; ++ ((char *)u)[i_len + 1] = 0; ++ } + + /* Do case conversions */ + if (CHECK_FLAGS(flags, CONV_TOUPPER)) { +@@ -1031,12 +1060,21 @@ size_t convert_charset ( charset_t from_ + strlower_w(u); + } + +- /* Convert UCS2 to to_set */ +- if ((size_t)(-1) == ( o_len = push_charset_flags( to_set, cap_charset, (char *)u, i_len, dest, dest_len, flags )) ) { +- LOG(log_error, logtype_default, +- "Conversion failed (CH_UCS2 to %s):%s", charset_name(to_set), strerror(errno)); ++ if (dest_len < 2) { ++ errno = E2BIG; + return (size_t) -1; + } ++ ++ { ++ size_t dest_capacity = dest_len - 2; ++ ++ /* Convert UCS2 to to_set */ ++ if ((size_t)(-1) == ( o_len = push_charset_flags( to_set, cap_charset, (char *)u, i_len, dest, dest_capacity, flags )) ) { ++ LOG(log_error, logtype_default, ++ "Conversion failed (CH_UCS2 to %s):%s", charset_name(to_set), strerror(errno)); ++ return (size_t) -1; ++ } ++ } + /* null terminate */ + dest[o_len] = 0; + dest[o_len +1] = 0; +--- netatalk-4.2.3~ds.orig/libatalk/util/server_child.c ++++ netatalk-4.2.3~ds/libatalk/util/server_child.c +@@ -148,6 +148,11 @@ int server_child_remove(server_child_t * + child->afpch_clientid = NULL; + } + ++ if (child->afpch_sessiontoken) { ++ free(child->afpch_sessiontoken); ++ child->afpch_sessiontoken = NULL; ++ } ++ + /* In main:child_handler() we need the fd in order to remove it from the pollfd set */ + fd = child->afpch_ipc_fd; + if (fd != -1) +@@ -177,6 +182,9 @@ void server_child_free(server_child_t *c + free(child->afpch_clientid); + if (child->afpch_volumes) + free(child->afpch_volumes); ++ if (child->afpch_sessiontoken) { ++ free(child->afpch_sessiontoken); ++ } + free(child); + child = tmp; + } +@@ -217,51 +225,63 @@ static int kill_child(afp_child_t *child + } + + /*! +- * Try to find an old session and pass socket ++ * @brief Try to find an old session by token and pass socket + * @returns -1 on error, 0 if no matching session was found, 1 if session was found and socket passed + */ + int server_child_transfer_session(server_child_t *children, +- pid_t pid, ++ const char *token, ++ uint32_t tokenlen, + uid_t uid, + int afp_socket, + uint16_t DSI_requestID) + { + EC_INIT; +- afp_child_t *child; ++ afp_child_t *child = NULL; ++ int i; ++ pthread_mutex_lock(&children->servch_lock); ++ ++ for (i = 0; i < CHILD_HASHSIZE && child == NULL; i++) { ++ afp_child_t *c; + +- if ((child = server_child_resolve(children, pid)) == NULL) { +- LOG(log_note, logtype_default, "Reconnect: no child[%u]", pid); +- if (kill(pid, 0) == 0) { +- LOG(log_note, logtype_default, "Reconnect: terminating old session[%u]", pid); +- kill(pid, SIGTERM); +- sleep(2); +- if (kill(pid, 0) == 0) { +- LOG(log_error, logtype_default, "Reconnect: killing old session[%u]", pid); +- kill(pid, SIGKILL); +- sleep(2); ++ for (c = children->servch_table[i]; c; c = c->afpch_next) { ++ if (c->afpch_sessiontoken_len == tokenlen ++ && c->afpch_sessiontoken != NULL ++ && memcmp(c->afpch_sessiontoken, token, tokenlen) == 0) { ++ child = c; ++ break; + } + } ++ } ++ ++ pthread_mutex_unlock(&children->servch_lock); ++ ++ if (child == NULL) { ++ LOG(log_note, logtype_default, ++ "Reconnect: no child with matching session token"); + return 0; + } + + if (!child->afpch_valid) { +- /* hmm, client 'guess' the pid, rogue? */ +- LOG(log_error, logtype_default, "Reconnect: invalidated child[%u]", pid); ++ LOG(log_error, logtype_default, "Reconnect: child[%u] not yet valid", ++ child->afpch_pid); + return 0; + } else if (child->afpch_uid != uid) { +- LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user", pid); ++ LOG(log_error, logtype_default, "Reconnect: child[%u] not the same user", ++ child->afpch_pid); + return 0; + } + +- LOG(log_note, logtype_default, "Reconnect: transferring session to child[%u]", pid); ++ LOG(log_note, logtype_default, "Reconnect: transferring session to child[%u]", ++ child->afpch_pid); + + if (writet(child->afpch_ipc_fd, &DSI_requestID, 2, 0, 2) != 2) { +- LOG(log_error, logtype_default, "Reconnect: error sending DSI id to child[%u]", pid); ++ LOG(log_error, logtype_default, "Reconnect: error sending DSI id to child[%u]", ++ child->afpch_pid); + EC_STATUS(-1); + goto EC_CLEANUP; + } + EC_ZERO_LOG(send_fd(child->afpch_ipc_fd, afp_socket)); +- EC_ZERO_LOG(kill(pid, SIGURG)); ++ EC_ZERO_LOG(kill(child->afpch_pid, SIGURG)); + + EC_STATUS(1); + +@@ -269,6 +289,36 @@ EC_CLEANUP: + EC_EXIT; + } + ++void server_child_set_token(server_child_t *children, pid_t pid, ++ const char *token, uint32_t tokenlen) ++{ ++ afp_child_t *child; ++ char *buf; ++ ++ if ((buf = malloc(tokenlen)) == NULL) { ++ return; ++ } ++ ++ memcpy(buf, token, tokenlen); ++ pthread_mutex_lock(&children->servch_lock); ++ ++ if ((child = server_child_resolve(children, pid)) != NULL) { ++ if (child->afpch_sessiontoken) { ++ free(child->afpch_sessiontoken); ++ } ++ ++ child->afpch_sessiontoken = buf; ++ child->afpch_sessiontoken_len = tokenlen; ++ buf = NULL; ++ } ++ ++ pthread_mutex_unlock(&children->servch_lock); ++ ++ if (buf) { ++ free(buf); ++ } ++} ++ + + /* see if there is a process for the same mac */ + /* if the times don't match mac has been rebooted */ +--- netatalk-4.2.3~ds.orig/libatalk/util/server_ipc.c ++++ netatalk-4.2.3~ds/libatalk/util/server_ipc.c +@@ -45,29 +45,38 @@ static char *ipc_cmd_str[] = { "IPC_DISC + "IPC_GETSESSION", + "IPC_STATE", + "IPC_VOLUMES", +- "IPC_LOGINDONE"}; ++ "IPC_LOGINDONE", ++ "IPC_SESSIONTOKEN" ++ }; + +-/* +- * Pass afp_socket to old disconnected session if one has a matching token (token = pid) ++/*! ++ * @brief Pass afp_socket to old disconnected session matching the random session token + * @returns -1 on error, 0 if no matching session was found, 1 if session was found and socket passed + */ + static int ipc_kill_token(struct ipc_header *ipc, server_child_t *children) + { +- pid_t pid; +- +- if (ipc->len != sizeof(pid_t)) { ++ if (ipc->len == 0 || ipc->msg == NULL) { + return -1; + } +- /* assume signals SA_RESTART set */ +- memcpy (&pid, ipc->msg, sizeof(pid_t)); + + return server_child_transfer_session(children, +- pid, ++ ipc->msg, ++ ipc->len, + ipc->uid, + ipc->afp_socket, + ipc->DSI_requestID); + } + ++static int ipc_session_token(struct ipc_header *ipc, server_child_t *children) ++{ ++ if (ipc->len == 0 || ipc->msg == NULL) { ++ return -1; ++ } ++ ++ server_child_set_token(children, ipc->child_pid, ipc->msg, ipc->len); ++ return 0; ++} ++ + /* ----------------- */ + static int ipc_get_session(struct ipc_header *ipc, server_child_t *children) + { +@@ -271,6 +280,13 @@ int ipc_server_read(server_child_t *chil + return -1; + break; + ++ case IPC_SESSIONTOKEN: ++ if (ipc_session_token(&ipc, children) != 0) { ++ return -1; ++ } ++ ++ break; ++ + default: + LOG (log_info, logtype_afpd, "ipc_read: unknown command: %d", ipc.command); + return -1; +--- netatalk-4.2.3~ds.orig/libatalk/vfs/ea_ad.c ++++ netatalk-4.2.3~ds/libatalk/vfs/ea_ad.c +@@ -617,6 +617,11 @@ char *ea_path(const struct ea * restrict + strlcat(pathbuf, "::EA", MAXPATHLEN + 1); + + if (eaname) { ++ if (strchr(eaname, '/') != NULL) { ++ LOG(log_warning, logtype_afpd, "ea_path: EA name contains '/': \"%s\"", eaname); ++ return NULL; ++ } ++ + strlcat(pathbuf, "::", MAXPATHLEN + 1); + if (macname) + if ((eaname = mtoupath(ea->vol, eaname)) == NULL) +@@ -1697,7 +1702,6 @@ int ea_chmod_dir(VFS_FUNC_ARGS_SETDIRUNI + int ret = AFP_OK; + unsigned int count = 0; + const char *eaname; +- const char *eaname_safe = NULL; + struct ea ea; + + LOG(log_debug, logtype_afpd, "ea_chmod_dir('%s')", name); +@@ -1730,16 +1734,7 @@ int ea_chmod_dir(VFS_FUNC_ARGS_SETDIRUNI + /* Set mode on EA files */ + while (count < ea.ea_count) { + eaname = (*ea.ea_entries)[count].ea_name; +- /* +- * Be careful with EA names from the EA header! +- * E.g. NFS users might have access to them, can inject paths using ../ or /..... +- * FIXME: +- * Until the EA code escapes / in EA name requests from the client, these therefor wont work. +- */ +- if ((eaname_safe = strrchr(eaname, '/'))) { +- LOG(log_warning, logtype_afpd, "ea_chmod_dir('%s'): contains a slash", eaname); +- eaname = eaname_safe; +- } ++ + if ((eaname = ea_path(&ea, eaname, 1)) == NULL) { + ret = AFPERR_MISC; + goto exit; diff -Nru netatalk-4.2.3~ds/debian/patches/series netatalk-4.2.3~ds/debian/patches/series --- netatalk-4.2.3~ds/debian/patches/series 2025-10-05 21:11:55.000000000 +0000 +++ netatalk-4.2.3~ds/debian/patches/series 2026-05-16 14:41:29.000000000 +0000 @@ -1,2 +1,3 @@ 001_uams_non_reentrant.patch 202_privacy.patch +002_CVE_batch_2026-05.patch