static uchar recv_ida_entries(int f, ida_entries *ent)
{
uchar computed_mask_bits = 0;
- int i, count = read_varint(f);
+ int i, count = read_varint_bounded(f, 0, MAX_WIRE_ACL_COUNT, "ACL count");
ent->idas = count ? new_array(id_access, count) : NULL;
ent->count = count;
}
if (xflags & XMIT_MOD_NSEC)
#ifndef CAN_SET_NSEC
- (void)read_varint(f);
+ (void)read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
#else
- modtime_nsec = read_varint(f);
+ modtime_nsec = read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
else
modtime_nsec = 0;
#endif
#endif
}
#endif
- if (!(xflags & XMIT_SAME_MODE))
+ if (!(xflags & XMIT_SAME_MODE)) {
mode = from_wire_mode(read_int(f));
+ /* Reject modes whose type bits are not one of the standard
+ * file types; otherwise garbage mode values propagate through
+ * the file-type checks below unpredictably. */
+ if (!S_ISREG(mode) && !S_ISDIR(mode) && !S_ISLNK(mode)
+ && !S_ISCHR(mode) && !S_ISBLK(mode)
+ && !S_ISFIFO(mode) && !S_ISSOCK(mode)) {
+ rprintf(FERROR, "invalid file mode 0%o for %s [%s]\n",
+ (unsigned)mode, lastname, who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ }
if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
atime = read_varlong(f, 4);
#if SIZEOF_TIME_T < SIZEOF_INT64
return u.x;
}
+/* Read an int32 and verify lo <= v <= hi. On out-of-range, abort with a
+ * protocol error naming "what". The bound is co-located with the read so it
+ * cannot be forgotten by a downstream user. */
+int32 read_int_bounded(int f, int32 lo, int32 hi, const char *what)
+{
+ int32 v = read_int(f);
+ if (v < lo || v > hi) {
+ rprintf(FERROR, "wire value %s out of range: %ld not in [%ld,%ld] [%s]\n",
+ what, (long)v, (long)lo, (long)hi, who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ return v;
+}
+
+/* As read_int_bounded but for varint-encoded values. */
+int32 read_varint_bounded(int f, int32 lo, int32 hi, const char *what)
+{
+ int32 v = read_varint(f);
+ if (v < lo || v > hi) {
+ rprintf(FERROR, "wire value %s out of range: %ld not in [%ld,%ld] [%s]\n",
+ what, (long)v, (long)lo, (long)hi, who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ return v;
+}
+
+/* Read a varint that will be used as a size_t. Rejects negative values
+ * (which would wrap to ~SIZE_MAX) and values exceeding the supplied max. */
+size_t read_varint_size(int f, size_t max, const char *what)
+{
+ int32 v = read_varint(f);
+ if (v < 0 || (size_t)v > max) {
+ rprintf(FERROR, "wire size %s out of range: %ld > %lu [%s]\n",
+ what, (long)v, (unsigned long)max, who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ return (size_t)v;
+}
+
int64 read_longint(int f)
{
#if SIZEOF_INT64 >= 8
(long)sum->count, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
+ /* Guard against integer overflow in downstream allocations sized by
+ * count*element_size. my_alloc uses divide-not-multiply so it is
+ * already wraparound-safe, but checking here gives a clearer error
+ * and also covers the (size_t)count * xfer_sum_len arithmetic that
+ * is performed *before* reaching my_alloc. */
+ if (xfer_sum_len > 0 && (size_t)sum->count > SIZE_MAX / (size_t)xfer_sum_len) {
+ rprintf(FERROR, "Invalid checksum count %ld (too large) [%s]\n",
+ (long)sum->count, who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ if ((size_t)sum->count > SIZE_MAX / sizeof(struct sum_buf)) {
+ rprintf(FERROR, "Invalid checksum count %ld (sum_buf overflow) [%s]\n",
+ (long)sum->count, who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
sum->blength = read_int(f);
if (sum->blength < 0 || sum->blength > max_blength) {
rprintf(FERROR, "Invalid block length %ld [%s]\n",
void read_del_stats(int f)
{
- stats.deleted_files = read_varint(f);
- stats.deleted_files += stats.deleted_dirs = read_varint(f);
- stats.deleted_files += stats.deleted_symlinks = read_varint(f);
- stats.deleted_files += stats.deleted_devices = read_varint(f);
- stats.deleted_files += stats.deleted_specials = read_varint(f);
+ stats.deleted_files = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_files");
+ stats.deleted_files += stats.deleted_dirs = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_dirs");
+ stats.deleted_files += stats.deleted_symlinks = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_symlinks");
+ stats.deleted_files += stats.deleted_devices = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_devices");
+ stats.deleted_files += stats.deleted_specials = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_specials");
}
static void become_copy_as_user()
/* For compatibility with older rsyncs */
#define OLD_MAX_BLOCK_SIZE ((int32)1 << 29)
+/* Policy ceilings on attacker-controlled wire values. Picked well above any
+ * legitimate filesystem / protocol traffic but well below sizes that could
+ * cause integer overflow or DoS-grade allocations. See input_checking.txt.
+ *
+ * Note on MAX_WIRE_XATTR_DATALEN: xattr datum size is bounded only by the
+ * wire-format maximum (signed int32 varint, ~2GB). macOS resource forks
+ * are transferred as the com.apple.ResourceFork xattr and can legitimately
+ * be many GB; --max-alloc (default 1GB, configurable) is the real
+ * allocation cap. read_varint_size() still rejects negative values so a
+ * hostile peer cannot wrap to ~SIZE_MAX. */
+#define MAX_WIRE_XATTR_COUNT 65536
+#define MAX_WIRE_XATTR_NAMELEN 4096
+#define MAX_WIRE_XATTR_DATALEN ((int32)0x7fffffff)
+#define MAX_WIRE_ACL_COUNT 65536
+#define MAX_WIRE_NSEC 999999999
+#define MAX_WIRE_DEL_STAT ((int32)1 << 30)
+
#define ROUND_UP_1024(siz) ((siz) & (1024-1) ? ((siz) | (1024-1)) + 1 : (siz))
#define IOERR_GENERAL (1<<0) /* For backward compatibility, this must == 1 */
rxa = lst->items;
num = 0;
while ((rel_pos = read_varint(f_in)) != 0) {
+ /* Detect signed overflow before the accumulating add. A hostile
+ * peer could otherwise wrap 'num' to land on an arbitrary value. */
+ if ((rel_pos > 0 && num > INT_MAX - rel_pos)
+ || (rel_pos < 0 && num < INT_MIN - rel_pos)) {
+ rprintf(FERROR, "xattr rel_pos accumulation overflow [%s]\n", who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
num += rel_pos;
if (am_sender) {
/* The sender-related num values are only in order on the sender.
}
old_datum = rxa->datum;
- rxa->datum_len = read_varint(f_in);
+ rxa->datum_len = read_varint_size(f_in, MAX_WIRE_XATTR_DATALEN, "xattr datum_len");
if (SIZE_MAX - rxa->name_len < rxa->datum_len)
overflow_exit("recv_xattr_request");
return;
}
- if ((count = read_varint(f)) != 0) {
+ count = read_varint_bounded(f, 0, MAX_WIRE_XATTR_COUNT, "xattr count");
+ if (count != 0) {
(void)EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, count);
temp_xattr.count = 0;
}
for (num = 1; num <= count; num++) {
char *ptr, *name;
rsync_xa *rxa;
- size_t name_len = read_varint(f);
- size_t datum_len = read_varint(f);
+ size_t name_len = read_varint_size(f, MAX_WIRE_XATTR_NAMELEN, "xattr name_len");
+ size_t datum_len = read_varint_size(f, MAX_WIRE_XATTR_DATALEN, "xattr datum_len");
size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + (size_t)xattr_sum_len : datum_len;
size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0;
if (SIZE_MAX - dget_len < extra_len || SIZE_MAX - dget_len - extra_len < name_len)