+ if (bytes > 0)
+ for (name = namebuf; name < namebuf + bytes; name += strlen(name) + 1)
+ (void)fremovexattr(s->dst_fd, name, 0);
+
+ free(namebuf);
+ }
+ else if (bytes < 0)
+ {
+ if (errno != ENOTSUP && errno != EPERM)
+ goto exit;
+ }
+
+ /*
+ * Extract the extended attributes.
+ *
+ * >>> WARNING <<<
+ * This assumes that the data is already in memory (not
+ * the case when there are lots of attributes or one of
+ * the attributes is very large.
+ */
+ if (adhdr->entries[0].length > FINDERINFOSIZE)
+ {
+ attr_header_t *attrhdr;
+ attr_entry_t *entry;
+ int count;
+ int i;
+
+ if ((size_t)hdrsize < sizeof(attr_header_t)) {
+ copyfile_warn("bad attribute header: %zu < %zu", hdrsize, sizeof(attr_header_t));
+ error = -1;
+ goto exit;
+ }
+
+ attrhdr = (attr_header_t *)buffer;
+ swap_attrhdr(attrhdr);
+ if (attrhdr->magic != ATTR_HDR_MAGIC)
+ {
+ if (COPYFILE_VERBOSE & s->flags)
+ copyfile_warn("bad attribute header");
+ error = -1;
+ goto exit;
+ }
+ count = attrhdr->num_attrs;
+ entry = (attr_entry_t *)&attrhdr[1];
+
+ for (i = 0; i < count; i++)
+ {
+ /*
+ * First we do some simple sanity checking.
+ * +) See if entry is within the buffer's range;
+ *
+ * +) Check the attribute name length; if it's longer than the
+ * maximum, we truncate it down. (We could error out as well;
+ * I'm not sure which is the better way to go here.)
+ *
+ * +) If, given the name length, it goes beyond the end of
+ * the buffer, error out.
+ *
+ * +) If the last byte isn't a NUL, make it a NUL. (Since we
+ * truncated the name length above, we truncate the name here.)
+ *
+ * +) If entry->offset is so large that it causes dataptr to
+ * go beyond the end of the buffer -- or, worse, so large that
+ * it wraps around! -- we error out.
+ *
+ * +) If entry->length would cause the entry to go beyond the
+ * end of the buffer (or, worse, wrap around to before it),
+ * *or* if the length is larger than the hdrsize, we error out.
+ * (An explanation of that: what we're checking for there is
+ * the small range of values such that offset+length would cause
+ * it to go beyond endptr, and then wrap around past buffer. We
+ * care about this because we are passing entry->length down to
+ * fgetxattr() below, and an erroneously large value could cause
+ * problems there. By making sure that it's less than hdrsize,
+ * which has already been sanity-checked above, we're safe.
+ * That may mean that the check against < buffer is unnecessary.)
+ */
+ if ((void*)entry >= endptr || (void*)entry < buffer) {
+ if (COPYFILE_VERBOSE & s->flags)
+ copyfile_warn("Incomplete or corrupt attribute entry");
+ error = -1;
+ s->err = EINVAL;
+ goto exit;
+ }
+
+ if (((char*)entry + sizeof(*entry)) > (char*)endptr) {
+ if (COPYFILE_VERBOSE & s->flags)
+ copyfile_warn("Incomplete or corrupt attribute entry");
+ error = -1;
+ s->err = EINVAL;
+ goto exit;
+ }
+
+ /*
+ * Endian swap the entry we're looking at. Previously
+ * we did this swap as part of swap_attrhdr, but that
+ * allowed a maliciously constructed file to overrun
+ * our allocation. Instead do the swap after we've verified
+ * the entry struct is within the buffer's range.
+ */
+ swap_attrhdr_entry(entry);
+
+ if (entry->namelen < 2) {
+ if (COPYFILE_VERBOSE & s->flags)
+ copyfile_warn("Corrupt attribute entry (only %d bytes)", entry->namelen);
+ error = -1;
+ s->err = EINVAL;
+ goto exit;
+ }
+
+ if (entry->namelen > XATTR_MAXNAMELEN + 1) {
+ if (COPYFILE_VERBOSE & s->flags)
+ copyfile_warn("Corrupt attribute entry (name length is %d bytes)", entry->namelen);
+ error = -1;
+ s->err = EINVAL;
+ goto exit;
+ }
+
+ if ((void*)(entry->name + entry->namelen) > endptr) {
+ if (COPYFILE_VERBOSE & s->flags)
+ copyfile_warn("Incomplete or corrupt attribute entry");
+ error = -1;
+ s->err = EINVAL;
+ goto exit;
+ }
+
+ /* Because namelen includes the NUL, we check one byte back */
+ if (entry->name[entry->namelen-1] != 0) {
+ if (COPYFILE_VERBOSE & s->flags)
+ copyfile_warn("Corrupt attribute entry (name is not NUL-terminated)");
+ error = -1;
+ s->err = EINVAL;
+ goto exit;
+ }