From ba5f34e68f11e76c57a095e70f7b22518561de55 Mon Sep 17 00:00:00 2001
From: Daniel Markstedt <daniel@mindani.net>
Date: Sat, 23 May 2026 14:16:31 +0200
Subject: [PATCH] CVE-2026-49387,CVE-2026-49388: afpd: bound Spotlight
 unmarshalling reads

Reported-by: Michalis Vasileiadis (@vmihalis)
Signed-off-by: Daniel Markstedt <daniel@mindani.net>
Reviewed-by: Andy Lemin (@andylemin)
---
 etc/afpd/spotlight.c             |  20 +-
 etc/afpd/spotlight_marshalling.c | 310 ++++++++++++++++++++++++++-----
 include/atalk/spotlight.h        |   4 +-
 3 files changed, 283 insertions(+), 51 deletions(-)

diff --git a/etc/afpd/spotlight.c b/etc/afpd/spotlight.c
index 2cece88a2..6cda5afc2 100644
--- a/etc/afpd/spotlight.c
+++ b/etc/afpd/spotlight.c
@@ -1381,6 +1381,11 @@ int afp_spotlight_rpc(AFPObj *obj, char *ibuf, size_t ibuflen,
     }
 
     spotlight_init(obj);
+
+    if (ibuflen < 2) {
+        return AFPERR_PARAM;
+    }
+
     slq_dump();
     /*
      * Process finished glib events
@@ -1394,7 +1399,13 @@ int afp_spotlight_rpc(AFPObj *obj, char *ibuf, size_t ibuflen,
     slq_cancelled_cleanup();
     ibuf += 2;
     ibuflen -= 2;
-    vid = SVAL(ibuf, 0);
+
+    if (ibuflen < 10) {
+        ret = AFPERR_PARAM;
+        goto EC_CLEANUP;
+    }
+
+    vid = (uint16_t)SVAL(ibuf, 0);
     LOG(log_debug, logtype_sl, "afp_spotlight_rpc(vid: %" PRIu16 ")", vid);
 
     if ((vol = getvolbyvid(vid)) == NULL) {
@@ -1427,9 +1438,14 @@ int afp_spotlight_rpc(AFPObj *obj, char *ibuf, size_t ibuflen,
         break;
 
     case SPOTLIGHT_CMD_RPC:
+        if (ibuflen < 22) {
+            ret = AFPERR_PARAM;
+            goto EC_CLEANUP;
+        }
+
         EC_NULL(query = talloc_zero(tmp_ctx, DALLOC_CTX));
         EC_NULL(reply = talloc_zero(tmp_ctx, DALLOC_CTX));
-        EC_NEG1_LOG(sl_unpack(query, ibuf + 22));
+        EC_NEG1_LOG(sl_unpack_len(query, ibuf + 22, ibuflen - 22));
         LOG(log_debug, logtype_sl, "Spotlight RPC request:\n%s",
             dd_dump(query, 0));
         EC_NULL_LOG(rpccmd = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
diff --git a/etc/afpd/spotlight_marshalling.c b/etc/afpd/spotlight_marshalling.c
index 0d61903aa..1d490df2d 100644
--- a/etc/afpd/spotlight_marshalling.c
+++ b/etc/afpd/spotlight_marshalling.c
@@ -18,6 +18,7 @@
 
 #include <errno.h>
 #include <inttypes.h>
+#include <limits.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -70,12 +71,14 @@
 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,
-                          int depth);
+                          uint count, size_t buf_len, const uint toc_offset,
+                          uint toc_count, 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);
+                         size_t buf_len, const uint toc_offset, uint toc_count,
+                         const uint encoding, int depth);
+static int sl_unpack_r(DALLOC_CTX *query, const char *buf, size_t buf_len,
+                       int depth);
 
 /**************************************************************************************************
  * Wrapper functions for the *VAL macros with bound checking
@@ -454,6 +457,13 @@ static int sl_pack_loop(DALLOC_CTX *query, char *buf, int offset, char *toc_buf,
  * unmarshalling functions
  **************************************************************************************************/
 
+static bool sl_unpack_range_valid(size_t buf_len, int offset, size_t length)
+{
+    return offset >= 0
+           && (size_t)offset <= buf_len
+           && length <= buf_len - (size_t)offset;
+}
+
 static uint64_t sl_unpack_uint64(const char *buf, int offset, uint encoding)
 {
     if (encoding == SL_ENC_LITTLE_ENDIAN) {
@@ -463,18 +473,64 @@ static uint64_t sl_unpack_uint64(const char *buf, int offset, uint encoding)
     }
 }
 
+static int sl_unpack_uint64_checked(const char *buf, size_t buf_len, int offset,
+                                    uint encoding, uint64_t *value)
+{
+    if (!sl_unpack_range_valid(buf_len, offset, sizeof(uint64_t))) {
+        return -1;
+    }
+
+    *value = sl_unpack_uint64(buf, offset, encoding);
+    return 0;
+}
+
+static int sl_unpack_count(uint64_t query_data64, int query_length,
+                           size_t element_size, int *count)
+{
+    uint64_t count64;
+
+    if (query_length < (int)sizeof(uint64_t)) {
+        return -1;
+    }
+
+    count64 = query_data64 >> 32;
+
+    if (count64 > INT_MAX
+            || count64 > (uint64_t)((query_length - (int)sizeof(uint64_t))
+                                    / (int)element_size)) {
+        LOG(log_error, logtype_sl,
+            "Spotlight unmarshalling rejected element count: "
+            "count=%" PRIu64 ", query_length=%d, element_size=%zu",
+            count64, query_length, element_size);
+        return -1;
+    }
+
+    *count = (int)count64;
+    return 0;
+}
+
 static int sl_unpack_ints(DALLOC_CTX *query, const char *buf, int offset,
-                          uint encoding)
+                          int query_length, size_t buf_len, uint encoding)
 {
     int count, i;
     uint64_t query_data64;
-    query_data64 = sl_unpack_uint64(buf, offset, encoding);
-    count = query_data64 >> 32;
+
+    if (sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                 &query_data64) < 0
+            || sl_unpack_count(query_data64, query_length, sizeof(uint64_t),
+                               &count) < 0) {
+        return -1;
+    }
+
     offset += 8;
     i = 0;
 
     while (i++ < count) {
-        query_data64 = sl_unpack_uint64(buf, offset, encoding);
+        if (sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                     &query_data64) < 0) {
+            return -1;
+        }
+
         dalloc_add_copy(query, &query_data64, uint64_t);
         offset += 8;
     }
@@ -483,18 +539,29 @@ static int sl_unpack_ints(DALLOC_CTX *query, const char *buf, int offset,
 }
 
 static int sl_unpack_date(DALLOC_CTX *query, const char *buf, int offset,
-                          uint encoding)
+                          int query_length, size_t buf_len, uint encoding)
 {
     int count, i;
     uint64_t query_data64;
     sl_time_t t;
-    query_data64 = sl_unpack_uint64(buf, offset, encoding);
-    count = query_data64 >> 32;
+
+    if (sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                 &query_data64) < 0
+            || sl_unpack_count(query_data64, query_length, sizeof(uint64_t),
+                               &count) < 0) {
+        return -1;
+    }
+
     offset += 8;
     i = 0;
 
     while (i++ < count) {
-        query_data64 = sl_unpack_uint64(buf, offset, encoding) >> 24;
+        if (sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                     &query_data64) < 0) {
+            return -1;
+        }
+
+        query_data64 >>= 24;
         t.tv_sec = query_data64 - SPOTLIGHT_TIME_DELTA;
         t.tv_usec = 0;
         dalloc_add_copy(query, &t, sl_time_t);
@@ -505,17 +572,27 @@ static int sl_unpack_date(DALLOC_CTX *query, const char *buf, int offset,
 }
 
 static int sl_unpack_uuid(DALLOC_CTX *query, const char *buf, int offset,
-                          uint encoding)
+                          int query_length, size_t buf_len, uint encoding)
 {
     int count, i;
     uint64_t query_data64;
     sl_uuid_t uuid;
-    query_data64 = sl_unpack_uint64(buf, offset, encoding);
-    count = query_data64 >> 32;
+
+    if (sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                 &query_data64) < 0
+            || sl_unpack_count(query_data64, query_length, sizeof(uuid),
+                               &count) < 0) {
+        return -1;
+    }
+
     offset += 8;
     i = 0;
 
     while (i++ < count) {
+        if (!sl_unpack_range_valid(buf_len, offset, sizeof(uuid))) {
+            return -1;
+        }
+
         memcpy(uuid.sl_uuid, buf + offset, 16);
         dalloc_add_copy(query, &uuid, sl_uuid_t);
         offset += 16;
@@ -525,7 +602,7 @@ static int sl_unpack_uuid(DALLOC_CTX *query, const char *buf, int offset,
 }
 
 static int sl_unpack_floats(DALLOC_CTX *query, const char *buf, int offset,
-                            uint encoding)
+                            int query_length, size_t buf_len, uint encoding)
 {
     int count, i;
     uint64_t query_data64;
@@ -533,12 +610,22 @@ static int sl_unpack_floats(DALLOC_CTX *query, const char *buf, int offset,
         double d;
         uint32_t w[2];
     } ieee_fp_union;
-    query_data64 = sl_unpack_uint64(buf, offset, encoding);
-    count = query_data64 >> 32;
+
+    if (sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                 &query_data64) < 0
+            || sl_unpack_count(query_data64, query_length, sizeof(uint64_t),
+                               &count) < 0) {
+        return -1;
+    }
+
     offset += 8;
     i = 0;
 
     while (i++ < count) {
+        if (!sl_unpack_range_valid(buf_len, offset, sizeof(uint64_t))) {
+            return -1;
+        }
+
         if (encoding == SL_ENC_LITTLE_ENDIAN) {
 #ifdef WORDS_BIGENDIAN
             ieee_fp_union.w[0] = IVAL(buf, offset + 4);
@@ -565,7 +652,7 @@ static int sl_unpack_floats(DALLOC_CTX *query, const char *buf, int offset,
 }
 
 static int sl_unpack_CNID(DALLOC_CTX *query, const char *buf, int offset,
-                          int length, uint encoding)
+                          int length, size_t buf_len, uint encoding)
 {
     EC_INIT;
     int count;
@@ -580,14 +667,25 @@ static int sl_unpack_CNID(DALLOC_CTX *query, const char *buf, int offset,
         goto EC_CLEANUP;
     }
 
-    query_data64 = sl_unpack_uint64(buf, offset, encoding);
+    if (!sl_unpack_range_valid(buf_len, offset, (size_t)(length - 8))
+            || sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                        &query_data64) < 0) {
+        EC_FAIL;
+    }
+
     count = query_data64 & 0xffff;
+
+    if (count > (length - 2 * (int)sizeof(uint64_t)) / (int)sizeof(uint64_t)) {
+        EC_FAIL;
+    }
+
     cnids->ca_unkn1 = (query_data64 & 0xffff0000) >> 16;
     cnids->ca_context = query_data64 >> 32;
     offset += 8;
 
     while (count --) {
-        query_data64 = sl_unpack_uint64(buf, offset, encoding);
+        EC_NEG1(sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                         &query_data64));
         dalloc_add_copy(cnids->ca_cnids, &query_data64, uint64_t);
         offset += 8;
     }
@@ -657,7 +755,9 @@ static int sl_unpack_cpx(DALLOC_CTX *query,
                          const int offset,
                          uint cpx_query_type,
                          uint cpx_query_count,
+                         size_t buf_len,
                          const uint toc_offset,
+                         uint toc_count,
                          const uint encoding,
                          int depth)
 {
@@ -668,7 +768,7 @@ static int sl_unpack_cpx(DALLOC_CTX *query,
     }
 
     int roffset = offset;
-    uint64_t query_data64;
+    uint64_t query_data64, used_in_last_block64;
     uint unicode_encoding;
     uint8_t mark_exists;
     char *p, *tmp;
@@ -681,24 +781,37 @@ static int sl_unpack_cpx(DALLOC_CTX *query,
     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, depth + 1));
+                                             buf_len, toc_offset, toc_count,
+                                             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, depth + 1));
+                                             buf_len, toc_offset, toc_count,
+                                             encoding, depth + 1));
         dalloc_add(query, sl_dict, sl_dict_t);
         break;
 
     case SQ_CPX_TYPE_STRING:
     case SQ_CPX_TYPE_UTF16_STRING:
-        query_data64 = sl_unpack_uint64(buf, offset, encoding);
+        EC_NEG1(sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                         &query_data64));
         qlen = (query_data64 & 0xffff) * 8;
-        used_in_last_block = query_data64 >> 32;
+        used_in_last_block64 = query_data64 >> 32;
+
+        if (used_in_last_block64 > 8) {
+            EC_FAIL;
+        }
+
+        used_in_last_block = (int)used_in_last_block64;
         slen = qlen - 16 + used_in_last_block;
 
+        if (qlen <= 8 || !sl_unpack_range_valid(buf_len, offset, qlen)) {
+            EC_FAIL;
+        }
+
         if (cpx_query_type == SQ_CPX_TYPE_STRING) {
             if (slen < 0 || offset + 8 + slen > (int)toc_offset) {
                 EC_FAIL;
@@ -735,14 +848,16 @@ static int sl_unpack_cpx(DALLOC_CTX *query,
         break;
 
     case SQ_CPX_TYPE_FILEMETA:
-        query_data64 = sl_unpack_uint64(buf, offset, encoding);
+        EC_NEG1(sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                         &query_data64));
         qlen = (query_data64 & 0xffff) * 8;
 
-        if (qlen <= 8) {
+        if (qlen <= 8 || !sl_unpack_range_valid(buf_len, offset, qlen)) {
             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_r(sl_fm, buf + offset + 8, depth + 1));
+            EC_NEG1_LOG(sl_unpack_r(sl_fm, buf + offset + 8, qlen - 8,
+                                    depth + 1));
             dalloc_add(query, sl_fm, sl_filemeta_t);
         }
 
@@ -750,9 +865,16 @@ static int sl_unpack_cpx(DALLOC_CTX *query,
         break;
 
     case SQ_CPX_TYPE_CNIDS:
-        query_data64 = sl_unpack_uint64(buf, offset, encoding);
+        EC_NEG1(sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                         &query_data64));
         qlen = (query_data64 & 0xffff) * 8;
-        EC_NEG1_LOG(sl_unpack_CNID(query, buf, offset + 8, qlen, encoding));
+
+        if (qlen <= 8 || !sl_unpack_range_valid(buf_len, offset, qlen)) {
+            EC_FAIL;
+        }
+
+        EC_NEG1_LOG(sl_unpack_CNID(query, buf, offset + 8, qlen, buf_len,
+                                   encoding));
         roffset += qlen;
         break;
 
@@ -773,7 +895,9 @@ static int sl_unpack_loop(DALLOC_CTX *query,
                           const char *buf,
                           int offset,
                           uint count,
+                          size_t buf_len,
                           const uint toc_offset,
+                          uint toc_count,
                           const uint encoding,
                           int depth)
 {
@@ -783,15 +907,18 @@ static int sl_unpack_loop(DALLOC_CTX *query,
         EC_FAIL;
     }
 
-    int i, toc_index, query_length;
+    int i, query_length;
     uint subcount;
     uint64_t query_data64, query_type;
+    uint64_t toc_index64;
+    size_t toc_index;
     uint cpx_query_type, cpx_query_count;
     sl_nil_t nil;
     sl_bool_t b;
 
     while (count > 0 && (offset < toc_offset)) {
-        query_data64 = sl_unpack_uint64(buf, offset, encoding);
+        EC_NEG1(sl_unpack_uint64_checked(buf, buf_len, offset, encoding,
+                                         &query_data64));
         query_length = (query_data64 & 0xffff) * 8;
         query_type = (query_data64 & 0xffff0000) >> 16;
 
@@ -799,15 +926,46 @@ static int sl_unpack_loop(DALLOC_CTX *query,
             EC_FAIL;
         }
 
+        if (!sl_unpack_range_valid(buf_len, offset, (size_t)query_length)) {
+            EC_FAIL;
+        }
+
         switch (query_type) {
         case SQ_TYPE_COMPLEX:
-            toc_index = (query_data64 >> 32) - 1;
-            query_data64 = sl_unpack_uint64(buf, toc_offset + toc_index * 8, encoding);
+            toc_index64 = query_data64 >> 32;
+
+            if (toc_index64 == 0 || toc_index64 > toc_count) {
+                LOG(log_error, logtype_sl,
+                    "Spotlight unmarshalling rejected TOC index: "
+                    "index=%" PRIu64 ", toc_count=%u",
+                    toc_index64, toc_count);
+                EC_FAIL;
+            }
+
+            toc_index = (size_t)(toc_index64 - 1);
+
+            if (toc_index > (SIZE_MAX - toc_offset) / sizeof(uint64_t)
+                    || toc_offset + toc_index * sizeof(uint64_t) > INT_MAX
+                    || !sl_unpack_range_valid(buf_len,
+                                              (int)(toc_offset
+                                                    + toc_index * sizeof(uint64_t)),
+                                              sizeof(uint64_t))) {
+                LOG(log_error, logtype_sl,
+                    "Spotlight unmarshalling rejected TOC offset: "
+                    "index=%zu, toc_offset=%u, buf_len=%zu",
+                    toc_index, toc_offset, buf_len);
+                EC_FAIL;
+            }
+
+            query_data64 = sl_unpack_uint64(buf,
+                                            (int)(toc_offset
+                                                  + toc_index * sizeof(uint64_t)),
+                                            encoding);
             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,
-                                               depth + 1));
+                                               cpx_query_count, buf_len, toc_offset,
+                                               toc_count, encoding, depth + 1));
             count--;
             break;
 
@@ -840,7 +998,8 @@ static int sl_unpack_loop(DALLOC_CTX *query,
             break;
 
         case SQ_TYPE_INT64:
-            EC_NEG1_LOG(subcount = sl_unpack_ints(query, buf, offset, encoding));
+            EC_NEG1_LOG(subcount = sl_unpack_ints(query, buf, offset, query_length,
+                                                  buf_len, encoding));
 
             if (subcount > count) {
                 EC_FAIL;
@@ -851,7 +1010,8 @@ static int sl_unpack_loop(DALLOC_CTX *query,
             break;
 
         case SQ_TYPE_UUID:
-            EC_NEG1_LOG(subcount = sl_unpack_uuid(query, buf, offset, encoding));
+            EC_NEG1_LOG(subcount = sl_unpack_uuid(query, buf, offset, query_length,
+                                                  buf_len, encoding));
 
             if (subcount > count) {
                 EC_FAIL;
@@ -862,7 +1022,9 @@ static int sl_unpack_loop(DALLOC_CTX *query,
             break;
 
         case SQ_TYPE_FLOAT:
-            EC_NEG1_LOG(subcount = sl_unpack_floats(query, buf, offset, encoding));
+            EC_NEG1_LOG(subcount = sl_unpack_floats(query, buf, offset,
+                                                    query_length, buf_len,
+                                                    encoding));
 
             if (subcount > count) {
                 EC_FAIL;
@@ -873,7 +1035,8 @@ static int sl_unpack_loop(DALLOC_CTX *query,
             break;
 
         case SQ_TYPE_DATE:
-            EC_NEG1_LOG(subcount = sl_unpack_date(query, buf, offset, encoding));
+            EC_NEG1_LOG(subcount = sl_unpack_date(query, buf, offset, query_length,
+                                                  buf_len, encoding));
 
             if (subcount > count) {
                 EC_FAIL;
@@ -929,11 +1092,21 @@ int sl_pack(DALLOC_CTX *query, char *buf)
     return len;
 }
 
-static int sl_unpack_r(DALLOC_CTX *query, const char *buf, int depth)
+static int sl_unpack_r(DALLOC_CTX *query, const char *buf, size_t buf_len,
+                       int depth)
 {
     EC_INIT;
     int encoding;
-    uint64_t toc_offset;
+    uint64_t query_data64;
+    uint64_t toc_offset64;
+    uint64_t toc_entry_count64;
+    size_t data_len;
+    uint toc_offset;
+    uint toc_entry_count;
+
+    if (buf_len < 16) {
+        EC_FAIL;
+    }
 
     if (strncmp(buf, "md031234", 8) == 0) {
         encoding = SL_ENC_BIG_ENDIAN;
@@ -942,19 +1115,60 @@ static int sl_unpack_r(DALLOC_CTX *query, const char *buf, int depth)
     }
 
     buf += 8;
-    toc_offset = ((sl_unpack_uint64(buf, 0, encoding) >> 32) - 1) * 8;
+    buf_len -= 8;
+    EC_NEG1(sl_unpack_uint64_checked(buf, buf_len, 0, encoding,
+                                     &query_data64));
+    toc_offset64 = ((query_data64 >> 32) - 1) * 8;
+    buf += 8;
+    buf_len -= 8;
+    data_len = buf_len;
+
+    /*
+     * From here buf/buf_len cover only the post-header data region;
+     * toc_offset is relative to this region and must point at the TOC tag.
+     */
+    if (toc_offset64 > 65000
+            || !sl_unpack_range_valid(data_len, (int)toc_offset64,
+                                      sizeof(uint64_t))) {
+        EC_FAIL_LOG("Spotlight unmarshalling rejected TOC offset: "
+                    "toc_offset=%" PRIu64 ", data_len=%zu",
+                    toc_offset64, data_len);
+    }
+
+    toc_offset = (uint)toc_offset64;
+    EC_NEG1(sl_unpack_uint64_checked(buf, buf_len, toc_offset, encoding,
+                                     &query_data64));
 
-    if (toc_offset > 65000) {
+    if (((query_data64 & 0xffff0000) >> 16) != SQ_TYPE_TOC) {
         EC_FAIL;
     }
 
-    buf += 8;
-    EC_NEG1(sl_unpack_loop(query, buf, 0, 1, toc_offset + 8, encoding, depth));
+    toc_entry_count64 = query_data64 & 0xffff;
+
+    if (toc_entry_count64 == 0) {
+        EC_FAIL;
+    }
+
+    toc_entry_count64--;
+
+    if (toc_entry_count64 > UINT_MAX
+            || toc_entry_count64 > (buf_len - (toc_offset + 8)) / 8) {
+        EC_FAIL;
+    }
+
+    toc_entry_count = (uint)toc_entry_count64;
+    EC_NEG1(sl_unpack_loop(query, buf, 0, 1, buf_len, toc_offset + 8,
+                           toc_entry_count, encoding, depth));
 EC_CLEANUP:
     EC_EXIT;
 }
 
 int sl_unpack(DALLOC_CTX *query, const char *buf)
 {
-    return sl_unpack_r(query, buf, 0);
+    return sl_unpack_r(query, buf, MAX_SLQ_DAT, 0);
+}
+
+int sl_unpack_len(DALLOC_CTX *query, const char *buf, size_t buf_len)
+{
+    return sl_unpack_r(query, buf, buf_len, 0);
 }
diff --git a/include/atalk/spotlight.h b/include/atalk/spotlight.h
index 3bf4414ed..923ed2e33 100644
--- a/include/atalk/spotlight.h
+++ b/include/atalk/spotlight.h
@@ -19,8 +19,9 @@
 #ifndef SPOTLIGHT_H
 #define SPOTLIGHT_H
 
-#include <stdint.h>
 #include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
 
 #include <gio/gio.h>
 
@@ -123,6 +124,7 @@ extern int afp_spotlight_rpc(AFPObj *obj, char *ibuf, size_t ibuflen _U_,
                              char *rbuf, size_t *rbuflen);
 extern int sl_pack(DALLOC_CTX *query, char *buf);
 extern int sl_unpack(DALLOC_CTX *query, const char *buf);
+extern int sl_unpack_len(DALLOC_CTX *query, const char *buf, size_t buf_len);
 extern void configure_spotlight_attributes(const char *attributes);
 
 #endif /* SPOTLIGHT_H */
