static int process_head_file(struct archive_read* a, struct rar5* rar,
    struct archive_entry* entry, size_t block_flags)
{
	ssize_t extra_data_size = 0;
	size_t data_size = 0;
	size_t file_flags = 0;
	size_t file_attr = 0;
	size_t compression_info = 0;
	size_t host_os = 0;
	size_t name_size = 0;
	uint64_t unpacked_size, window_size;
	uint32_t mtime = 0, crc = 0;
	int c_method = 0, c_version = 0;
	char name_utf8_buf[MAX_NAME_IN_BYTES];
	const uint8_t* p;

	enum FILE_FLAGS {
		DIRECTORY = 0x0001, UTIME = 0x0002, CRC32 = 0x0004,
		UNKNOWN_UNPACKED_SIZE = 0x0008,
	};

	enum FILE_ATTRS {
		ATTR_READONLY = 0x1, ATTR_HIDDEN = 0x2, ATTR_SYSTEM = 0x4,
		ATTR_DIRECTORY = 0x10,
	};

	enum COMP_INFO_FLAGS {
		SOLID = 0x0040,
	};

	enum HOST_OS {
		HOST_WINDOWS = 0,
		HOST_UNIX = 1,
	};

	archive_entry_clear(entry);

	/* Do not reset file context if we're switching archives. */
	if(!rar->cstate.switch_multivolume) {
		reset_file_context(rar);
	}

	if(block_flags & HFL_EXTRA_DATA) {
		size_t edata_size = 0;
		if(!read_var_sized(a, &edata_size, NULL))
			return ARCHIVE_EOF;

		/* Intentional type cast from unsigned to signed. */
		extra_data_size = (ssize_t) edata_size;
	}

	if(block_flags & HFL_DATA) {
		if(!read_var_sized(a, &data_size, NULL))
			return ARCHIVE_EOF;

		rar->file.bytes_remaining = data_size;
	} else {
		rar->file.bytes_remaining = 0;

		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
				"no data found in file/service block");
		return ARCHIVE_FATAL;
	}

	if(!read_var_sized(a, &file_flags, NULL))
		return ARCHIVE_EOF;

	if(!read_var(a, &unpacked_size, NULL))
		return ARCHIVE_EOF;

	if(file_flags & UNKNOWN_UNPACKED_SIZE) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER,
		    "Files with unknown unpacked size are not supported");
		return ARCHIVE_FATAL;
	}

	rar->file.dir = (uint8_t) ((file_flags & DIRECTORY) > 0);

	if(!read_var_sized(a, &file_attr, NULL))
		return ARCHIVE_EOF;

	if(file_flags & UTIME) {
		if(!read_u32(a, &mtime))
			return ARCHIVE_EOF;
	}

	if(file_flags & CRC32) {
		if(!read_u32(a, &crc))
			return ARCHIVE_EOF;
	}

	if(!read_var_sized(a, &compression_info, NULL))
		return ARCHIVE_EOF;

	c_method = (int) (compression_info >> 7) & 0x7;
	c_version = (int) (compression_info & 0x3f);

	/* RAR5 seems to limit the dictionary size to 64MB. */
	window_size = (rar->file.dir > 0) ?
		0 :
		g_unpack_window_size << ((compression_info >> 10) & 15);
	rar->cstate.method = c_method;
	rar->cstate.version = c_version + 50;
	rar->file.solid = (compression_info & SOLID) > 0;

	/* Archives which declare solid files without initializing the window
	 * buffer first are invalid. */

	if(rar->file.solid > 0 && rar->cstate.window_buf == NULL) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
				  "Declared solid file, but no window buffer "
				  "initialized yet.");
		return ARCHIVE_FATAL;
	}

	/* Check if window_size is a sane value. Also, if the file is not
	 * declared as a directory, disallow window_size == 0. */
	if(window_size > (64 * 1024 * 1024) ||
	    (rar->file.dir == 0 && window_size == 0))
	{
		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
		    "Declared dictionary size is not supported.");
		return ARCHIVE_FATAL;
	}

	if(rar->file.solid > 0) {
		/* Re-check if current window size is the same as previous
		 * window size (for solid files only). */
		if(rar->file.solid_window_size > 0 &&
		    rar->file.solid_window_size != (ssize_t) window_size)
		{
			archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
			    "Window size for this solid file doesn't match "
			    "the window size used in previous solid file. ");
			return ARCHIVE_FATAL;
		}
	}

	/* If we're currently switching volumes, ignore the new definition of
	 * window_size. */
	if(rar->cstate.switch_multivolume == 0) {
		/* Values up to 64M should fit into ssize_t on every
		 * architecture. */
		rar->cstate.window_size = (ssize_t) window_size;
	}

	if(rar->file.solid > 0 && rar->file.solid_window_size == 0) {
		/* Solid files have to have the same window_size across
		   whole archive. Remember the window_size parameter
		   for first solid file found. */
		rar->file.solid_window_size = rar->cstate.window_size;
	}

	init_window_mask(rar);

	rar->file.service = 0;

	if(!read_var_sized(a, &host_os, NULL))
		return ARCHIVE_EOF;

	if(host_os == HOST_WINDOWS) {
		/* Host OS is Windows */

		__LA_MODE_T mode;

		if(file_attr & ATTR_DIRECTORY) {
			if (file_attr & ATTR_READONLY) {
				mode = 0555 | AE_IFDIR;
			} else {
				mode = 0755 | AE_IFDIR;
			}
		} else {
			if (file_attr & ATTR_READONLY) {
				mode = 0444 | AE_IFREG;
			} else {
				mode = 0644 | AE_IFREG;
			}
		}

		archive_entry_set_mode(entry, mode);

		if (file_attr & (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM)) {
			char *fflags_text, *ptr;
			/* allocate for "rdonly,hidden,system," */
			fflags_text = malloc(22 * sizeof(char));
			if (fflags_text != NULL) {
				ptr = fflags_text;
				if (file_attr & ATTR_READONLY) {
					strcpy(ptr, "rdonly,");
					ptr = ptr + 7;
				}
				if (file_attr & ATTR_HIDDEN) {
					strcpy(ptr, "hidden,");
					ptr = ptr + 7;
				}
				if (file_attr & ATTR_SYSTEM) {
					strcpy(ptr, "system,");
					ptr = ptr + 7;
				}
				if (ptr > fflags_text) {
					/* Delete trailing comma */
					*(ptr - 1) = '\0';
					archive_entry_copy_fflags_text(entry,
					    fflags_text);
				}
				free(fflags_text);
			}
		}
	} else if(host_os == HOST_UNIX) {
		/* Host OS is Unix */
		archive_entry_set_mode(entry, (__LA_MODE_T) file_attr);
	} else {
		/* Unknown host OS */
		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
				"Unsupported Host OS: 0x%x", (int) host_os);

		return ARCHIVE_FATAL;
	}

	if(!read_var_sized(a, &name_size, NULL))
		return ARCHIVE_EOF;

	if(!read_ahead(a, name_size, &p))
		return ARCHIVE_EOF;

	if(name_size > (MAX_NAME_IN_CHARS - 1)) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
				"Filename is too long");

		return ARCHIVE_FATAL;
	}

	if(name_size == 0) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
				"No filename specified");

		return ARCHIVE_FATAL;
	}

	memcpy(name_utf8_buf, p, name_size);
	name_utf8_buf[name_size] = 0;
	if(ARCHIVE_OK != consume(a, name_size)) {
		return ARCHIVE_EOF;
	}

	archive_entry_update_pathname_utf8(entry, name_utf8_buf);

	if(extra_data_size > 0) {
		int ret = process_head_file_extra(a, entry, rar,
		    extra_data_size);

		/*
		 * TODO: rewrite or remove useless sanity check
		 *       as extra_data_size is not passed as a pointer
		 *
		if(extra_data_size < 0) {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER,
			    "File extra data size is not zero");
			return ARCHIVE_FATAL;
		}
		 */

		if(ret != ARCHIVE_OK)
			return ret;
	}

	if((file_flags & UNKNOWN_UNPACKED_SIZE) == 0) {
		rar->file.unpacked_size = (ssize_t) unpacked_size;
		if(rar->file.redir_type == REDIR_TYPE_NONE)
			archive_entry_set_size(entry, unpacked_size);
	}

	if(file_flags & UTIME) {
		archive_entry_set_mtime(entry, (time_t) mtime, 0);
	}

	if(file_flags & CRC32) {
		rar->file.stored_crc32 = crc;
	}

	if(!rar->cstate.switch_multivolume) {
		/* Do not reinitialize unpacking state if we're switching
		 * archives. */
		rar->cstate.block_parsing_finished = 1;
		rar->cstate.all_filters_applied = 1;
		rar->cstate.initialized = 0;
	}

	if(rar->generic.split_before > 0) {
		/* If now we're standing on a header that has a 'split before'
		 * mark, it means we're standing on a 'continuation' file
		 * header. Signal the caller that if it wants to move to
		 * another file, it must call rar5_read_header() function
		 * again. */

		return ARCHIVE_RETRY;
	} else {
		return ARCHIVE_OK;
	}
}


RAR5 reader: reject files that declare invalid header flags

One of the fields in RAR5's base block structure is the size of the
header. Some invalid files declare a 0 header size setting, which can
confuse the unpacker. Minimum header size for RAR5 base blocks is 7
bytes (4 bytes for CRC, and 3 bytes for the rest), so block size of 0
bytes should be rejected at header parsing stage.

The fix adds an error condition if header size of 0 bytes is detected.
In this case, the unpacker will not attempt to unpack the file, as the
header is corrupted.

The commit also adds OSSFuzz #20459 sample to test further regressions
in this area.

static inline int get_archive_read(struct archive* a,
    struct archive_read** ar)
{
	*ar = (struct archive_read*) a;
	archive_check_magic(a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW,
	    "archive_read_support_format_rar5");

	return ARCHIVE_OK;
}

archive_read_support_format_rar5.c in libarchive before 3.4.2 attempts to unpack a RAR5 file with an invalid or corrupted header (such as a header size of zero), leading to a SIGSEGV or possibly unspecified other impact.


static int parse_file_extra_version(struct archive_read* a,
    struct archive_entry* e, ssize_t* extra_data_size)
{
	size_t flags = 0;
	size_t version = 0;
	size_t value_len = 0;
	struct archive_string version_string;
	struct archive_string name_utf8_string;
	const char* cur_filename;

	/* Flags are ignored. */
	if(!read_var_sized(a, &flags, &value_len))
		return ARCHIVE_EOF;

	*extra_data_size -= value_len;
	if(ARCHIVE_OK != consume(a, value_len))
		return ARCHIVE_EOF;

	if(!read_var_sized(a, &version, &value_len))
		return ARCHIVE_EOF;

	*extra_data_size -= value_len;
	if(ARCHIVE_OK != consume(a, value_len))
		return ARCHIVE_EOF;

	/* extra_data_size should be zero here. */

	cur_filename = archive_entry_pathname_utf8(e);
	if(cur_filename == NULL) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER,
		    "Version entry without file name");
		return ARCHIVE_FATAL;
	}

	archive_string_init(&version_string);
	archive_string_init(&name_utf8_string);

	/* Prepare a ;123 suffix for the filename, where '123' is the version
	 * value of this file. */
	archive_string_sprintf(&version_string, ";%zu", version);

	/* Build the new filename. */
	archive_strcat(&name_utf8_string, cur_filename);
	archive_strcat(&name_utf8_string, version_string.s);

	/* Apply the new filename into this file's context. */
	archive_entry_update_pathname_utf8(e, name_utf8_string.s);

	/* Free buffers. */
	archive_string_free(&version_string);
	archive_string_free(&name_utf8_string);
	return ARCHIVE_OK;
}


static int uncompress_file(struct archive_read* a) {
	int ret;

	while(1) {
		/* Sometimes the uncompression function will return a
		 * 'retry' signal. If this will happen, we have to retry
		 * the function. */
		ret = do_uncompress_file(a);
		if(ret != ARCHIVE_RETRY)
			return ret;
	}
}


DEFINE_TEST(test_read_format_rar5_solid_skip_all_but_first)
{
	const char* reffile = "test_read_format_rar5_solid.rar";

	/* Extract first, skip rest */

	PROLOGUE(reffile);
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x7CCA70CD));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test5.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test6.bin", archive_entry_pathname(ae));
	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


static void cdeque_front_fast(struct cdeque* d, void** value) {
	*value = (void*) d->arr[d->beg_pos];
}


DEFINE_TEST(test_read_format_rar5_solid_skip_all)
{
	const char* reffile = "test_read_format_rar5_solid.rar";

	/* Skip all */

	PROLOGUE(reffile);
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test5.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test6.bin", archive_entry_pathname(ae));
	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


int archive_read_support_format_rar5(struct archive *_a) {
	struct archive_read* ar;
	int ret;
	struct rar5* rar;

	if(ARCHIVE_OK != (ret = get_archive_read(_a, &ar)))
		return ret;

	rar = malloc(sizeof(*rar));
	if(rar == NULL) {
		archive_set_error(&ar->archive, ENOMEM,
		    "Can't allocate rar5 data");
		return ARCHIVE_FATAL;
	}

	if(ARCHIVE_OK != rar5_init(rar)) {
		archive_set_error(&ar->archive, ENOMEM,
		    "Can't allocate rar5 filter buffer");
		return ARCHIVE_FATAL;
	}

	ret = __archive_read_register_format(ar,
	    rar,
	    "rar5",
	    rar5_bid,
	    rar5_options,
	    rar5_read_header,
	    rar5_read_data,
	    rar5_read_data_skip,
	    rar5_seek_data,
	    rar5_cleanup,
	    rar5_capabilities,
	    rar5_has_encrypted_entries);

	if(ret != ARCHIVE_OK) {
		(void) rar5_cleanup(ar);
	}

	return ret;
}


DEFINE_TEST(test_read_format_rar5_fileattr)
{
	unsigned long set, clear, flag;

	flag = 0;

	PROLOGUE("test_read_format_rar5_fileattr.rar");

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_mode(ae), 0444 | AE_IFREG);
	assertEqualString("readonly.txt", archive_entry_pathname(ae));
	assertEqualString("rdonly", archive_entry_fflags_text(ae));
	archive_entry_fflags(ae, &set, &clear);
#if defined(__FreeBSD__)
	flag = UF_READONLY;
#elif defined(_WIN32) && !defined(CYGWIN)
	flag = FILE_ATTRIBUTE_READONLY;
#endif
	assertEqualInt(flag, set & flag);

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_mode(ae), 0644 | AE_IFREG);
	assertEqualString("hidden.txt", archive_entry_pathname(ae));
	assertEqualString("hidden", archive_entry_fflags_text(ae));
	archive_entry_fflags(ae, &set, &clear);
#if defined(__FreeBSD__)
	flag = UF_HIDDEN;
#elif defined(_WIN32) && !defined(CYGWIN)
	flag = FILE_ATTRIBUTE_HIDDEN;
#endif
	assertEqualInt(flag, set & flag);

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_mode(ae), 0644 | AE_IFREG);
	assertEqualString("system.txt", archive_entry_pathname(ae));
	assertEqualString("system", archive_entry_fflags_text(ae));
	archive_entry_fflags(ae, &set, &clear);
#if defined(__FreeBSD__)
	flag = UF_SYSTEM;;
#elif defined(_WIN32) && !defined(CYGWIN)
	flag = FILE_ATTRIBUTE_SYSTEM;
#endif
	assertEqualInt(flag, set & flag);

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_mode(ae), 0444 | AE_IFREG);
	assertEqualString("ro_hidden.txt", archive_entry_pathname(ae));
	assertEqualString("rdonly,hidden", archive_entry_fflags_text(ae));
	archive_entry_fflags(ae, &set, &clear);
#if defined(__FreeBSD__)
	flag = UF_READONLY | UF_HIDDEN;
#elif defined(_WIN32) && !defined(CYGWIN)
	flag = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN;
#endif
	assertEqualInt(flag, set & flag);

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_mode(ae), 0555 | AE_IFDIR);
	assertEqualString("dir_readonly", archive_entry_pathname(ae));
	assertEqualString("rdonly", archive_entry_fflags_text(ae));
	archive_entry_fflags(ae, &set, &clear);
#if defined(__FreeBSD__)
	flag = UF_READONLY;
#elif defined(_WIN32) && !defined(CYGWIN)
	flag = FILE_ATTRIBUTE_READONLY;
#endif
	assertEqualInt(flag, set & flag);

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_mode(ae), 0755 | AE_IFDIR);
	assertEqualString("dir_hidden", archive_entry_pathname(ae));
	assertEqualString("hidden", archive_entry_fflags_text(ae));
	archive_entry_fflags(ae, &set, &clear);
#if defined(__FreeBSD__)
	flag = UF_HIDDEN;
#elif defined(_WIN32) && !defined(CYGWIN)
	flag = FILE_ATTRIBUTE_HIDDEN;
#endif
	assertEqualInt(flag, set & flag);

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_mode(ae), 0755 | AE_IFDIR);
	assertEqualString("dir_system", archive_entry_pathname(ae));
	assertEqualString("system", archive_entry_fflags_text(ae));
	archive_entry_fflags(ae, &set, &clear);
#if defined(__FreeBSD__)
	flag = UF_SYSTEM;
#elif defined(_WIN32) && !defined(CYGWIN)
	flag = FILE_ATTRIBUTE_SYSTEM;
#endif
	assertEqualInt(flag, set & flag);

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_mode(ae), 0555 | AE_IFDIR);
	assertEqualString("dir_rohidden", archive_entry_pathname(ae));
	assertEqualString("rdonly,hidden", archive_entry_fflags_text(ae));
	archive_entry_fflags(ae, &set, &clear);
#if defined(__FreeBSD__)
	flag = UF_READONLY | UF_HIDDEN;
#elif defined(_WIN32) && !defined(CYGWIN)
	flag = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN;
#endif
	assertEqualInt(flag, set & flag);

	EPILOGUE();
}


DEFINE_TEST(test_read_format_rar5_symlink)
{
	const int DATA_SIZE = 5;
	uint8_t buff[5];

	PROLOGUE("test_read_format_rar5_symlink.rar");

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("file.txt", archive_entry_pathname(ae));
	assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
	assertA((int) archive_entry_mtime(ae) > 0);
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("symlink.txt", archive_entry_pathname(ae));
	assertEqualInt(AE_IFLNK, archive_entry_filetype(ae));
	assertEqualString("file.txt", archive_entry_symlink(ae));
	assertEqualInt(AE_SYMLINK_TYPE_FILE, archive_entry_symlink_type(ae));
	assertA(0 == archive_read_data(a, NULL, archive_entry_size(ae)));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("dirlink", archive_entry_pathname(ae));
	assertEqualInt(AE_IFLNK, archive_entry_filetype(ae));
	assertEqualString("dir", archive_entry_symlink(ae));
	assertEqualInt(AE_SYMLINK_TYPE_DIRECTORY, archive_entry_symlink_type(ae));
	assertA(0 == archive_read_data(a, NULL, archive_entry_size(ae)));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("dir", archive_entry_pathname(ae));
	assertEqualInt(AE_IFDIR, archive_entry_filetype(ae));
	assertA(0 == archive_read_data(a, NULL, archive_entry_size(ae)));

	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));

	EPILOGUE();
}


static void init_window_mask(struct rar5* rar) {
	if (rar->cstate.window_size)
		rar->cstate.window_mask = rar->cstate.window_size - 1;
	else
		rar->cstate.window_mask = 0;
}


DEFINE_TEST(test_read_format_rar5_block_size_is_too_small)
{
	char buf[4096];
	PROLOGUE("test_read_format_rar5_block_size_is_too_small.rar");

	/* This file is damaged, so those functions should return failure.
	 * Additionally, SIGSEGV shouldn't be raised during execution
	 * of those functions. */

	assertA(archive_read_next_header(a, &ae) != ARCHIVE_OK);
	assertA(archive_read_data(a, buf, sizeof(buf)) <= 0);

	EPILOGUE();
}


static void* cdeque_filter(struct filter_info* f) {
	return (void**) (size_t) f;
}


static int parse_file_extra_owner(struct archive_read* a,
    struct archive_entry* e, ssize_t* extra_data_size)
{
	uint64_t flags = 0;
	uint64_t value_size = 0;
	uint64_t id = 0;
	size_t name_len = 0;
	size_t name_size = 0;
	char namebuf[OWNER_MAXNAMELEN];
	const uint8_t* p;

	if(!read_var(a, &flags, &value_size))
		return ARCHIVE_EOF;
	if(ARCHIVE_OK != consume(a, (int64_t)value_size))
		return ARCHIVE_EOF;
	*extra_data_size -= value_size;

	if ((flags & OWNER_USER_NAME) != 0) {
		if(!read_var_sized(a, &name_size, NULL))
			return ARCHIVE_EOF;
		*extra_data_size -= name_size + 1;

		if(!read_ahead(a, name_size, &p))
			return ARCHIVE_EOF;

		if (name_size >= OWNER_MAXNAMELEN) {
			name_len = OWNER_MAXNAMELEN - 1;
		} else {
			name_len = name_size;
		}

		memcpy(namebuf, p, name_len);
		namebuf[name_len] = 0;
		if(ARCHIVE_OK != consume(a, (int64_t)name_size))
			return ARCHIVE_EOF;

		archive_entry_set_uname(e, namebuf);
	}
	if ((flags & OWNER_GROUP_NAME) != 0) {
		if(!read_var_sized(a, &name_size, NULL))
			return ARCHIVE_EOF;
		*extra_data_size -= name_size + 1;

		if(!read_ahead(a, name_size, &p))
			return ARCHIVE_EOF;

		if (name_size >= OWNER_MAXNAMELEN) {
			name_len = OWNER_MAXNAMELEN - 1;
		} else {
			name_len = name_size;
		}

		memcpy(namebuf, p, name_len);
		namebuf[name_len] = 0;
		if(ARCHIVE_OK != consume(a, (int64_t)name_size))
			return ARCHIVE_EOF;

		archive_entry_set_gname(e, namebuf);
	}
	if ((flags & OWNER_USER_UID) != 0) {
		if(!read_var(a, &id, &value_size))
			return ARCHIVE_EOF;
		if(ARCHIVE_OK != consume(a, (int64_t)value_size))
			return ARCHIVE_EOF;
		*extra_data_size -= value_size;

		archive_entry_set_uid(e, (la_int64_t)id);
	}
	if ((flags & OWNER_GROUP_GID) != 0) {
		if(!read_var(a, &id, &value_size))
			return ARCHIVE_EOF;
		if(ARCHIVE_OK != consume(a, (int64_t)value_size))
			return ARCHIVE_EOF;
		*extra_data_size -= value_size;

		archive_entry_set_gid(e, (la_int64_t)id);
	}
	return ARCHIVE_OK;
}


static void reset_file_context(struct rar5* rar) {
	memset(&rar->file, 0, sizeof(rar->file));
	blake2sp_init(&rar->file.b2state, 32);

	if(rar->main.solid) {
		rar->cstate.solid_offset += rar->cstate.write_ptr;
	} else {
		rar->cstate.solid_offset = 0;
	}

	rar->cstate.write_ptr = 0;
	rar->cstate.last_write_ptr = 0;
	rar->cstate.last_unstore_ptr = 0;

	rar->file.redir_type = REDIR_TYPE_NONE;
	rar->file.redir_flags = 0;

	free_filters(rar);
}


DEFINE_TEST(test_read_format_rar5_multiarchive_solid_skip_all_but_last)
{
	const char* reffiles[] = {
		"test_read_format_rar5_multiarchive_solid.part01.rar",
		"test_read_format_rar5_multiarchive_solid.part02.rar",
		"test_read_format_rar5_multiarchive_solid.part03.rar",
		"test_read_format_rar5_multiarchive_solid.part04.rar",
		NULL
	};

	PROLOGUE_MULTI(reffiles);
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("cebula.txt", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test5.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test6.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("elf-Linux-ARMv7-ls", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x886F91EB));
	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


static int process_head_file_extra(struct archive_read* a,
    struct archive_entry* e, struct rar5* rar, ssize_t extra_data_size)
{
	size_t extra_field_size;
	size_t extra_field_id = 0;
	int ret = ARCHIVE_FATAL;
	size_t var_size;

	while(extra_data_size > 0) {
		if(!read_var_sized(a, &extra_field_size, &var_size))
			return ARCHIVE_EOF;

		extra_data_size -= var_size;
		if(ARCHIVE_OK != consume(a, var_size)) {
			return ARCHIVE_EOF;
		}

		if(!read_var_sized(a, &extra_field_id, &var_size))
			return ARCHIVE_EOF;

		extra_data_size -= var_size;
		if(ARCHIVE_OK != consume(a, var_size)) {
			return ARCHIVE_EOF;
		}

		switch(extra_field_id) {
			case EX_HASH:
				ret = parse_file_extra_hash(a, rar,
				    &extra_data_size);
				break;
			case EX_HTIME:
				ret = parse_file_extra_htime(a, e, rar,
				    &extra_data_size);
				break;
			case EX_REDIR:
				ret = parse_file_extra_redir(a, e, rar,
				    &extra_data_size);
				break;
			case EX_UOWNER:
				ret = parse_file_extra_owner(a, e,
				    &extra_data_size);
				break;
			case EX_VERSION:
				ret = parse_file_extra_version(a, e,
				    &extra_data_size);
				break;
			case EX_CRYPT:
				/* fallthrough */
			case EX_SUBDATA:
				/* fallthrough */
			default:
				/* Skip unsupported entry. */
				return consume(a, extra_data_size);
		}
	}

	if(ret != ARCHIVE_OK) {
		/* Attribute not implemented. */
		return ret;
	}

	return ARCHIVE_OK;
}


static int do_unpack(struct archive_read* a, struct rar5* rar,
    const void** buf, size_t* size, int64_t* offset)
{
	enum COMPRESSION_METHOD {
		STORE = 0, FASTEST = 1, FAST = 2, NORMAL = 3, GOOD = 4,
		BEST = 5
	};

	if(rar->file.service > 0) {
		return do_unstore_file(a, rar, buf, size, offset);
	} else {
		switch(rar->cstate.method) {
			case STORE:
				return do_unstore_file(a, rar, buf, size,
				    offset);
			case FASTEST:
				/* fallthrough */
			case FAST:
				/* fallthrough */
			case NORMAL:
				/* fallthrough */
			case GOOD:
				/* fallthrough */
			case BEST:
				return uncompress_file(a);
			default:
				archive_set_error(&a->archive,
				    ARCHIVE_ERRNO_FILE_FORMAT,
				    "Compression method not supported: 0x%x",
				    rar->cstate.method);

				return ARCHIVE_FATAL;
		}
	}

#if !defined WIN32
	/* Not reached. */
	return ARCHIVE_OK;
#endif
}


DEFINE_TEST(test_read_format_rar5_multiarchive_solid_skip_all_but_scnd)
{
	const char* reffiles[] = {
		"test_read_format_rar5_multiarchive_solid.part01.rar",
		"test_read_format_rar5_multiarchive_solid.part02.rar",
		"test_read_format_rar5_multiarchive_solid.part03.rar",
		"test_read_format_rar5_multiarchive_solid.part04.rar",
		NULL
	};

	PROLOGUE_MULTI(reffiles);
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("cebula.txt", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x7CCA70CD));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test5.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test6.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("elf-Linux-ARMv7-ls", archive_entry_pathname(ae));
	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


static void cdeque_clear(struct cdeque* d) {
	d->size = 0;
	d->beg_pos = 0;
	d->end_pos = 0;
}


static int bid_standard(struct archive_read* a) {
	const uint8_t* p;

	if(!read_ahead(a, rar5_signature_size, &p))
		return -1;

	if(!memcmp(rar5_signature, p, rar5_signature_size))
		return 30;

	return -1;
}


DEFINE_TEST(test_read_format_rar5_different_winsize_on_merge)
{
	char buf[4096];
	PROLOGUE("test_read_format_rar5_different_winsize_on_merge.rar");

	/* Return codes of those calls are ignored, because this sample file
	 * is invalid. However, the unpacker shouldn't produce any SIGSEGV
	 * errors during processing. */

	(void) archive_read_next_header(a, &ae);
	while(0 < archive_read_data(a, buf, sizeof(buf))) {}

	EPILOGUE();
}


static void skip_bits(struct rar5* rar, int bits) {
	const int new_bits = rar->bits.bit_addr + bits;
	rar->bits.in_addr += new_bits >> 3;
	rar->bits.bit_addr = new_bits & 7;
}


DEFINE_TEST(test_read_format_rar5_multiarchive_solid_extr_all)
{
	const char* reffiles[] = {
		"test_read_format_rar5_multiarchive_solid.part01.rar",
		"test_read_format_rar5_multiarchive_solid.part02.rar",
		"test_read_format_rar5_multiarchive_solid.part03.rar",
		"test_read_format_rar5_multiarchive_solid.part04.rar",
		NULL
	};

	PROLOGUE_MULTI(reffiles);
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("cebula.txt", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x7E5EC49E));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x7cca70cd));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x7e13b2c6));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0xf166afcb));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x9fb123d9));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x10c43ed4));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test5.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0xb9d155f2));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test6.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x36a448ff));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("elf-Linux-ARMv7-ls", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x886F91EB));

	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


DEFINE_TEST(test_read_format_rar5_readtables_overflow)
{
	uint8_t buf[16];

	PROLOGUE("test_read_format_rar5_readtables_overflow.rar");

	/* This archive is invalid. However, processing it shouldn't cause any
	 * buffer overflow errors during reading rar5 tables. */

	(void) archive_read_next_header(a, &ae);
	(void) archive_read_data(a, buf, sizeof(buf));
	(void) archive_read_next_header(a, &ae);

	EPILOGUE();
}


static int process_main_locator_extra_block(struct archive_read* a,
    struct rar5* rar)
{
	uint64_t locator_flags;

	enum LOCATOR_FLAGS {
		QLIST = 0x01, RECOVERY = 0x02,
	};

	if(!read_var(a, &locator_flags, NULL)) {
		return ARCHIVE_EOF;
	}

	if(locator_flags & QLIST) {
		if(!read_var(a, &rar->qlist_offset, NULL)) {
			return ARCHIVE_EOF;
		}

		/* qlist is not used */
	}

	if(locator_flags & RECOVERY) {
		if(!read_var(a, &rar->rr_offset, NULL)) {
			return ARCHIVE_EOF;
		}

		/* rr is not used */
	}

	return ARCHIVE_OK;
}


DEFINE_TEST(test_read_format_rar5_multiple_files_solid)
{
	const int DATA_SIZE = 4096;
	uint8_t buff[4096];

	PROLOGUE("test_read_format_rar5_multiple_files_solid.rar");

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));
	assertA(verify_data(buff, 1, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));
	assertA(verify_data(buff, 2, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));
	assertA(verify_data(buff, 3, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));
	assertA(verify_data(buff, 4, DATA_SIZE));

	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


static int merge_block(struct archive_read* a, ssize_t block_size,
    const uint8_t** p)
{
	struct rar5* rar = get_context(a);
	ssize_t cur_block_size, partial_offset = 0;
	const uint8_t* lp;
	int ret;

	if(rar->merge_mode) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER,
		    "Recursive merge is not allowed");

		return ARCHIVE_FATAL;
	}

	/* Set a flag that we're in the switching mode. */
	rar->cstate.switch_multivolume = 1;

	/* Reallocate the memory which will hold the whole block. */
	if(rar->vol.push_buf)
		free((void*) rar->vol.push_buf);

	/* Increasing the allocation block by 8 is due to bit reading functions,
	 * which are using additional 2 or 4 bytes. Allocating the block size
	 * by exact value would make bit reader perform reads from invalid
	 * memory block when reading the last byte from the buffer. */
	rar->vol.push_buf = malloc(block_size + 8);
	if(!rar->vol.push_buf) {
		archive_set_error(&a->archive, ENOMEM,
		    "Can't allocate memory for a merge block buffer.");
		return ARCHIVE_FATAL;
	}

	/* Valgrind complains if the extension block for bit reader is not
	 * initialized, so initialize it. */
	memset(&rar->vol.push_buf[block_size], 0, 8);

	/* A single block can span across multiple multivolume archive files,
	 * so we use a loop here. This loop will consume enough multivolume
	 * archive files until the whole block is read. */

	while(1) {
		/* Get the size of current block chunk in this multivolume
		 * archive file and read it. */
		cur_block_size = rar5_min(rar->file.bytes_remaining,
		    block_size - partial_offset);

		if(cur_block_size == 0) {
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_FILE_FORMAT,
			    "Encountered block size == 0 during block merge");
			return ARCHIVE_FATAL;
		}

		if(!read_ahead(a, cur_block_size, &lp))
			return ARCHIVE_EOF;

		/* Sanity check; there should never be a situation where this
		 * function reads more data than the block's size. */
		if(partial_offset + cur_block_size > block_size) {
			archive_set_error(&a->archive,
			    ARCHIVE_ERRNO_PROGRAMMER,
			    "Consumed too much data when merging blocks.");
			return ARCHIVE_FATAL;
		}

		/* Merge previous block chunk with current block chunk,
		 * or create first block chunk if this is our first
		 * iteration. */
		memcpy(&rar->vol.push_buf[partial_offset], lp, cur_block_size);

		/* Advance the stream read pointer by this block chunk size. */
		if(ARCHIVE_OK != consume(a, cur_block_size))
			return ARCHIVE_EOF;

		/* Update the pointers. `partial_offset` contains information
		 * about the sum of merged block chunks. */
		partial_offset += cur_block_size;
		rar->file.bytes_remaining -= cur_block_size;

		/* If `partial_offset` is the same as `block_size`, this means
		 * we've merged all block chunks and we have a valid full
		 * block. */
		if(partial_offset == block_size) {
			break;
		}

		/* If we don't have any bytes to read, this means we should
		 * switch to another multivolume archive file. */
		if(rar->file.bytes_remaining == 0) {
			rar->merge_mode++;
			ret = advance_multivolume(a);
			rar->merge_mode--;
			if(ret != ARCHIVE_OK) {
				return ret;
			}
		}
	}

	*p = rar->vol.push_buf;

	/* If we're here, we can resume unpacking by processing the block
	 * pointed to by the `*p` memory pointer. */

	return ARCHIVE_OK;
}


static int apply_filters(struct archive_read* a) {
	struct filter_info* flt;
	struct rar5* rar = get_context(a);
	int ret;

	rar->cstate.all_filters_applied = 0;

	/* Get the first filter that can be applied to our data. The data
	 * needs to be fully unpacked before the filter can be run. */
	if(CDE_OK == cdeque_front(&rar->cstate.filters,
	    cdeque_filter_p(&flt))) {
		/* Check if our unpacked data fully covers this filter's
		 * range. */
		if(rar->cstate.write_ptr > flt->block_start &&
		    rar->cstate.write_ptr >= flt->block_start +
		    flt->block_length) {
			/* Check if we have some data pending to be written
			 * right before the filter's start offset. */
			if(rar->cstate.last_write_ptr == flt->block_start) {
				/* Run the filter specified by descriptor
				 * `flt`. */
				ret = run_filter(a, flt);
				if(ret != ARCHIVE_OK) {
					/* Filter failure, return error. */
					return ret;
				}

				/* Filter descriptor won't be needed anymore
				 * after it's used, * so remove it from the
				 * filter list and free its memory. */
				(void) cdeque_pop_front(&rar->cstate.filters,
				    cdeque_filter_p(&flt));

				free(flt);
			} else {
				/* We can't run filters yet, dump the memory
				 * right before the filter. */
				push_window_data(a, rar,
				    rar->cstate.last_write_ptr,
				    flt->block_start);
			}

			/* Return 'filter applied or not needed' state to the
			 * caller. */
			return ARCHIVE_RETRY;
		}
	}

	rar->cstate.all_filters_applied = 1;
	return ARCHIVE_OK;
}


static int process_block(struct archive_read* a) {
	const uint8_t* p;
	struct rar5* rar = get_context(a);
	int ret;

	/* If we don't have any data to be processed, this most probably means
	 * we need to switch to the next volume. */
	if(rar->main.volume && rar->file.bytes_remaining == 0) {
		ret = advance_multivolume(a);
		if(ret != ARCHIVE_OK)
			return ret;
	}

	if(rar->cstate.block_parsing_finished) {
		ssize_t block_size;
		ssize_t to_skip;
		ssize_t cur_block_size;

		/* The header size won't be bigger than 6 bytes. */
		if(!read_ahead(a, 6, &p)) {
			/* Failed to prefetch data block header. */
			return ARCHIVE_EOF;
		}

		/*
		 * Read block_size by parsing block header. Validate the header
		 * by calculating CRC byte stored inside the header. Size of
		 * the header is not constant (block size can be stored either
		 * in 1 or 2 bytes), that's why block size is left out from the
		 * `compressed_block_header` structure and returned by
		 * `parse_block_header` as the second argument. */

		ret = parse_block_header(a, p, &block_size,
		    &rar->last_block_hdr);
		if(ret != ARCHIVE_OK) {
			return ret;
		}

		/* Skip block header. Next data is huffman tables,
		 * if present. */
		to_skip = sizeof(struct compressed_block_header) +
			bf_byte_count(&rar->last_block_hdr) + 1;

		if(ARCHIVE_OK != consume(a, to_skip))
			return ARCHIVE_EOF;

		rar->file.bytes_remaining -= to_skip;

		/* The block size gives information about the whole block size,
		 * but the block could be stored in split form when using
		 * multi-volume archives. In this case, the block size will be
		 * bigger than the actual data stored in this file. Remaining
		 * part of the data will be in another file. */

		cur_block_size =
			rar5_min(rar->file.bytes_remaining, block_size);

		if(block_size > rar->file.bytes_remaining) {
			/* If current blocks' size is bigger than our data
			 * size, this means we have a multivolume archive.
			 * In this case, skip all base headers until the end
			 * of the file, proceed to next "partXXX.rar" volume,
			 * find its signature, skip all headers up to the first
			 * FILE base header, and continue from there.
			 *
			 * Note that `merge_block` will update the `rar`
			 * context structure quite extensively. */

			ret = merge_block(a, block_size, &p);
			if(ret != ARCHIVE_OK) {
				return ret;
			}

			cur_block_size = block_size;

			/* Current stream pointer should be now directly
			 * *after* the block that spanned through multiple
			 * archive files. `p` pointer should have the data of
			 * the *whole* block (merged from partial blocks
			 * stored in multiple archives files). */
		} else {
			rar->cstate.switch_multivolume = 0;

			/* Read the whole block size into memory. This can take
			 * up to  8 megabytes of memory in theoretical cases.
			 * Might be worth to optimize this and use a standard
			 * chunk of 4kb's. */
			if(!read_ahead(a, 4 + cur_block_size, &p)) {
				/* Failed to prefetch block data. */
				return ARCHIVE_EOF;
			}
		}

		rar->cstate.block_buf = p;
		rar->cstate.cur_block_size = cur_block_size;
		rar->cstate.block_parsing_finished = 0;

		rar->bits.in_addr = 0;
		rar->bits.bit_addr = 0;

		if(bf_is_table_present(&rar->last_block_hdr)) {
			/* Load Huffman tables. */
			ret = parse_tables(a, rar, p);
			if(ret != ARCHIVE_OK) {
				/* Error during decompression of Huffman
				 * tables. */
				return ret;
			}
		}
	} else {
		/* Block parsing not finished, reuse previous memory buffer. */
		p = rar->cstate.block_buf;
	}

	/* Uncompress the block, or a part of it, depending on how many bytes
	 * will be generated by uncompressing the block.
	 *
	 * In case too many bytes will be generated, calling this function
	 * again will resume the uncompression operation. */
	ret = do_uncompress_block(a, p);
	if(ret != ARCHIVE_OK) {
		return ret;
	}

	if(rar->cstate.block_parsing_finished &&
	    rar->cstate.switch_multivolume == 0 &&
	    rar->cstate.cur_block_size > 0)
	{
		/* If we're processing a normal block, consume the whole
		 * block. We can do this because we've already read the whole
		 * block to memory. */
		if(ARCHIVE_OK != consume(a, rar->cstate.cur_block_size))
			return ARCHIVE_FATAL;

		rar->file.bytes_remaining -= rar->cstate.cur_block_size;
	} else if(rar->cstate.switch_multivolume) {
		/* Don't consume the block if we're doing multivolume
		 * processing. The volume switching function will consume
		 * the proper count of bytes instead. */
		rar->cstate.switch_multivolume = 0;
	}

	return ARCHIVE_OK;
}


static int skip_unprocessed_bytes(struct archive_read* a) {
	struct rar5* rar = get_context(a);
	int ret;

	if(rar->file.bytes_remaining) {
		/* Use different skipping method in block merging mode than in
		 * normal mode. If merge mode is active, rar5_read_data_skip
		 * can't be used, because it could allow recursive use of
		 * merge_block() * function, and this function doesn't support
		 * recursive use. */
		if(rar->merge_mode) {
			/* Discard whole merged block. This is valid in solid
			 * mode as well, because the code will discard blocks
			 * only if those blocks are safe to discard (i.e.
			 * they're not FILE blocks).  */
			ret = consume(a, rar->file.bytes_remaining);
			if(ret != ARCHIVE_OK) {
				return ret;
			}
			rar->file.bytes_remaining = 0;
		} else {
			/* If we're not in merge mode, use safe skipping code.
			 * This will ensure we'll handle solid archives
			 * properly. */
			ret = rar5_read_data_skip(a);
			if(ret != ARCHIVE_OK) {
				return ret;
			}
		}
	}

	return ARCHIVE_OK;
}


DEFINE_TEST(test_read_format_rar5_multiarchive_solid_skip_all_but_third)
{
	const char* reffiles[] = {
		"test_read_format_rar5_multiarchive_solid.part01.rar",
		"test_read_format_rar5_multiarchive_solid.part02.rar",
		"test_read_format_rar5_multiarchive_solid.part03.rar",
		"test_read_format_rar5_multiarchive_solid.part04.rar",
		NULL
	};

	PROLOGUE_MULTI(reffiles);
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("cebula.txt", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertA(0 == extract_one(a, ae, 0x7E13B2C6));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test5.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test6.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("elf-Linux-ARMv7-ls", archive_entry_pathname(ae));
	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


DEFINE_TEST(test_read_format_rar5_multiple_files)
{
	const int DATA_SIZE = 4096;
	uint8_t buff[4096];

	PROLOGUE("test_read_format_rar5_multiple_files.rar");

	/* There should be 4 files inside this test file. Check for their
	 * existence, and also check the contents of those test files. */

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));
	assertA(verify_data(buff, 1, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));
	assertA(verify_data(buff, 2, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));
	assertA(verify_data(buff, 3, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));
	assertA(verify_data(buff, 4, DATA_SIZE));

	/* There should be no more files in this archive. */

	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


static void circular_memcpy(uint8_t* dst, uint8_t* window, const uint64_t mask,
    int64_t start, int64_t end)
{
	if((start & mask) > (end & mask)) {
		ssize_t len1 = mask + 1 - (start & mask);
		ssize_t len2 = end & mask;

		memcpy(dst, &window[start & mask], len1);
		memcpy(dst + len1, window, len2);
	} else {
		memcpy(dst, &window[start & mask], (size_t) (end - start));
	}
}


static int parse_file_extra_redir(struct archive_read* a,
    struct archive_entry* e, struct rar5* rar, ssize_t* extra_data_size)
{
	uint64_t value_size = 0;
	size_t target_size = 0;
	char target_utf8_buf[MAX_NAME_IN_BYTES];
	const uint8_t* p;

	if(!read_var(a, &rar->file.redir_type, &value_size))
		return ARCHIVE_EOF;
	if(ARCHIVE_OK != consume(a, (int64_t)value_size))
		return ARCHIVE_EOF;
	*extra_data_size -= value_size;

	if(!read_var(a, &rar->file.redir_flags, &value_size))
		return ARCHIVE_EOF;
	if(ARCHIVE_OK != consume(a, (int64_t)value_size))
		return ARCHIVE_EOF;
	*extra_data_size -= value_size;

	if(!read_var_sized(a, &target_size, NULL))
		return ARCHIVE_EOF;
	*extra_data_size -= target_size + 1;

	if(!read_ahead(a, target_size, &p))
		return ARCHIVE_EOF;

	if(target_size > (MAX_NAME_IN_CHARS - 1)) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
		    "Link target is too long");
		return ARCHIVE_FATAL;
	}

	if(target_size == 0) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
		    "No link target specified");
		return ARCHIVE_FATAL;
	}

	memcpy(target_utf8_buf, p, target_size);
	target_utf8_buf[target_size] = 0;

	if(ARCHIVE_OK != consume(a, (int64_t)target_size))
		return ARCHIVE_EOF;

	switch(rar->file.redir_type) {
		case REDIR_TYPE_UNIXSYMLINK:
		case REDIR_TYPE_WINSYMLINK:
			archive_entry_set_filetype(e, AE_IFLNK);
			archive_entry_update_symlink_utf8(e, target_utf8_buf);
			if (rar->file.redir_flags & REDIR_SYMLINK_IS_DIR) {
				archive_entry_set_symlink_type(e,
					AE_SYMLINK_TYPE_DIRECTORY);
			} else {
				archive_entry_set_symlink_type(e,
				AE_SYMLINK_TYPE_FILE);
			}
			break;

		case REDIR_TYPE_HARDLINK:
			archive_entry_set_filetype(e, AE_IFREG);
			archive_entry_update_hardlink_utf8(e, target_utf8_buf);
			break;

		default:
			/* Unknown redir type, skip it. */
			break;
	}
	return ARCHIVE_OK;
}


static int do_uncompress_block(struct archive_read* a, const uint8_t* p) {
	struct rar5* rar = get_context(a);
	uint16_t num;
	int ret;

	const uint64_t cmask = rar->cstate.window_mask;
	const struct compressed_block_header* hdr = &rar->last_block_hdr;
	const uint8_t bit_size = 1 + bf_bit_size(hdr);

	while(1) {
		if(rar->cstate.write_ptr - rar->cstate.last_write_ptr >
		    (rar->cstate.window_size >> 1)) {
			/* Don't allow growing data by more than half of the
			 * window size at a time. In such case, break the loop;
			 *  next call to this function will continue processing
			 *  from this moment. */
			break;
		}

		if(rar->bits.in_addr > rar->cstate.cur_block_size - 1 ||
		    (rar->bits.in_addr == rar->cstate.cur_block_size - 1 &&
		    rar->bits.bit_addr >= bit_size))
		{
			/* If the program counter is here, it means the
			 * function has finished processing the block. */
			rar->cstate.block_parsing_finished = 1;
			break;
		}

		/* Decode the next literal. */
		if(ARCHIVE_OK != decode_number(a, &rar->cstate.ld, p, &num)) {
			return ARCHIVE_EOF;
		}

		/* Num holds a decompression literal, or 'command code'.
		 *
		 * - Values lower than 256 are just bytes. Those codes
		 *   can be stored in the output buffer directly.
		 *
		 * - Code 256 defines a new filter, which is later used to
		 *   ransform the data block accordingly to the filter type.
		 *   The data block needs to be fully uncompressed first.
		 *
		 * - Code bigger than 257 and smaller than 262 define
		 *   a repetition pattern that should be copied from
		 *   an already uncompressed chunk of data.
		 */

		if(num < 256) {
			/* Directly store the byte. */
			int64_t write_idx = rar->cstate.solid_offset +
			    rar->cstate.write_ptr++;

			rar->cstate.window_buf[write_idx & cmask] =
			    (uint8_t) num;
			continue;
		} else if(num >= 262) {
			uint16_t dist_slot;
			int len = decode_code_length(rar, p, num - 262),
				dbits,
				dist = 1;

			if(len == -1) {
				archive_set_error(&a->archive,
				    ARCHIVE_ERRNO_PROGRAMMER,
				    "Failed to decode the code length");

				return ARCHIVE_FATAL;
			}

			if(ARCHIVE_OK != decode_number(a, &rar->cstate.dd, p,
			    &dist_slot))
			{
				archive_set_error(&a->archive,
				    ARCHIVE_ERRNO_PROGRAMMER,
				    "Failed to decode the distance slot");

				return ARCHIVE_FATAL;
			}

			if(dist_slot < 4) {
				dbits = 0;
				dist += dist_slot;
			} else {
				dbits = dist_slot / 2 - 1;

				/* Cast to uint32_t will make sure the shift
				 * left operation won't produce undefined
				 * result. Then, the uint32_t type will
				 * be implicitly casted to int. */
				dist += (uint32_t) (2 |
				    (dist_slot & 1)) << dbits;
			}

			if(dbits > 0) {
				if(dbits >= 4) {
					uint32_t add = 0;
					uint16_t low_dist;

					if(dbits > 4) {
						if(ARCHIVE_OK != read_bits_32(
						    rar, p, &add)) {
							/* Return EOF if we
							 * can't read more
							 * data. */
							return ARCHIVE_EOF;
						}

						skip_bits(rar, dbits - 4);
						add = (add >> (
						    36 - dbits)) << 4;
						dist += add;
					}

					if(ARCHIVE_OK != decode_number(a,
					    &rar->cstate.ldd, p, &low_dist))
					{
						archive_set_error(&a->archive,
						    ARCHIVE_ERRNO_PROGRAMMER,
						    "Failed to decode the "
						    "distance slot");

						return ARCHIVE_FATAL;
					}

					if(dist >= INT_MAX - low_dist - 1) {
						/* This only happens in
						 * invalid archives. */
						archive_set_error(&a->archive,
						    ARCHIVE_ERRNO_FILE_FORMAT,
						    "Distance pointer "
						    "overflow");
						return ARCHIVE_FATAL;
					}

					dist += low_dist;
				} else {
					/* dbits is one of [0,1,2,3] */
					int add;

					if(ARCHIVE_OK != read_consume_bits(rar,
					     p, dbits, &add)) {
						/* Return EOF if we can't read
						 * more data. */
						return ARCHIVE_EOF;
					}

					dist += add;
				}
			}

			if(dist > 0x100) {
				len++;

				if(dist > 0x2000) {
					len++;

					if(dist > 0x40000) {
						len++;
					}
				}
			}

			dist_cache_push(rar, dist);
			rar->cstate.last_len = len;

			if(ARCHIVE_OK != copy_string(a, len, dist))
				return ARCHIVE_FATAL;

			continue;
		} else if(num == 256) {
			/* Create a filter. */
			ret = parse_filter(a, p);
			if(ret != ARCHIVE_OK)
				return ret;

			continue;
		} else if(num == 257) {
			if(rar->cstate.last_len != 0) {
				if(ARCHIVE_OK != copy_string(a,
				    rar->cstate.last_len,
				    rar->cstate.dist_cache[0]))
				{
					return ARCHIVE_FATAL;
				}
			}

			continue;
		} else {
			/* num < 262 */
			const int idx = num - 258;
			const int dist = dist_cache_touch(rar, idx);

			uint16_t len_slot;
			int len;

			if(ARCHIVE_OK != decode_number(a, &rar->cstate.rd, p,
			    &len_slot)) {
				return ARCHIVE_FATAL;
			}

			len = decode_code_length(rar, p, len_slot);
			rar->cstate.last_len = len;

			if(ARCHIVE_OK != copy_string(a, len, dist))
				return ARCHIVE_FATAL;

			continue;
		}

		/* The program counter shouldn't reach here. */
		archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
		    "Unsupported block code: 0x%x", num);

		return ARCHIVE_FATAL;
	}

	return ARCHIVE_OK;
}


static int rar5_capabilities(struct archive_read * a) {
	(void) a;
	return 0;
}


DEFINE_TEST(test_read_format_rar5_multiarchive_solid_skip_all)
{
	const char* reffiles[] = {
		"test_read_format_rar5_multiarchive_solid.part01.rar",
		"test_read_format_rar5_multiarchive_solid.part02.rar",
		"test_read_format_rar5_multiarchive_solid.part03.rar",
		"test_read_format_rar5_multiarchive_solid.part04.rar",
		NULL
	};

	PROLOGUE_MULTI(reffiles);
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("cebula.txt", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test1.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test2.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test3.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test4.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test5.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test6.bin", archive_entry_pathname(ae));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("elf-Linux-ARMv7-ls", archive_entry_pathname(ae));
	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));
	EPILOGUE();
}


static int read_var(struct archive_read* a, uint64_t* pvalue,
    uint64_t* pvalue_len)
{
	uint64_t result = 0;
	size_t shift, i;
	const uint8_t* p;
	uint8_t b;

	/* We will read maximum of 8 bytes. We don't have to handle the
	 * situation to read the RAR5 variable-sized value stored at the end of
	 * the file, because such situation will never happen. */
	if(!read_ahead(a, 8, &p))
		return 0;

	for(shift = 0, i = 0; i < 8; i++, shift += 7) {
		b = p[i];

		/* Strip the MSB from the input byte and add the resulting
		 * number to the `result`. */
		result += (b & (uint64_t)0x7F) << shift;

		/* MSB set to 1 means we need to continue decoding process.
		 * MSB set to 0 means we're done.
		 *
		 * This conditional checks for the second case. */
		if((b & 0x80) == 0) {
			if(pvalue) {
				*pvalue = result;
			}

			/* If the caller has passed the `pvalue_len` pointer,
			 * store the number of consumed bytes in it and do NOT
			 * consume those bytes, since the caller has all the
			 * information it needs to perform */
			if(pvalue_len) {
				*pvalue_len = 1 + i;
			} else {
				/* If the caller did not provide the
				 * `pvalue_len` pointer, it will not have the
				 * possibility to advance the file pointer,
				 * because it will not know how many bytes it
				 * needs to consume. This is why we handle
				 * such situation here automatically. */
				if(ARCHIVE_OK != consume(a, 1 + i)) {
					return 0;
				}
			}

			/* End of decoding process, return success. */
			return 1;
		}
	}

	/* The decoded value takes the maximum number of 8 bytes.
	 * It's a maximum number of bytes, so end decoding process here
	 * even if the first bit of last byte is 1. */
	if(pvalue) {
		*pvalue = result;
	}

	if(pvalue_len) {
		*pvalue_len = 9;
	} else {
		if(ARCHIVE_OK != consume(a, 9)) {
			return 0;
		}
	}

	return 1;
}


static int use_data(struct rar5* rar, const void** buf, size_t* size,
    int64_t* offset)
{
	int i;

	for(i = 0; i < rar5_countof(rar->cstate.dready); i++) {
		struct data_ready *d = &rar->cstate.dready[i];

		if(d->used) {
			if(buf)    *buf = d->buf;
			if(size)   *size = d->size;
			if(offset) *offset = d->offset;

			d->used = 0;
			return ARCHIVE_OK;
		}
	}

	return ARCHIVE_RETRY;
}


static int rar5_has_encrypted_entries(struct archive_read *_a) {
	(void) _a;

	/* Unsupported for now. */
	return ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED;
}


DEFINE_TEST(test_read_format_rar5_nonempty_dir_stream)
{
	uint8_t buf[16];

	PROLOGUE("test_read_format_rar5_nonempty_dir_stream.rar");

	/* This archive is invalid. However, processing it shouldn't cause any
	 * errors related to buffer overflows when using -fsanitize. */

	(void) archive_read_next_header(a, &ae);
	(void) archive_read_data(a, buf, sizeof(buf));
	(void) archive_read_next_header(a, &ae);

	EPILOGUE();
}


DEFINE_TEST(test_read_format_rar5_block_by_block)
{
	/* This test uses strange buffer sizes intentionally. */

	struct archive_entry *ae;
	struct archive *a;
	uint8_t buf[173];
	int bytes_read;
	uint32_t computed_crc = 0;

	extract_reference_file("test_read_format_rar5_compressed.rar");
	assert((a = archive_read_new()) != NULL);
	assertA(0 == archive_read_support_filter_all(a));
	assertA(0 == archive_read_support_format_all(a));
	assertA(0 == archive_read_open_filename(a, "test_read_format_rar5_compressed.rar", 130));
	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("test.bin", archive_entry_pathname(ae));
	assertEqualInt(1200, archive_entry_size(ae));

	/* File size is 1200 bytes, we're reading it using a buffer of 173 bytes.
	 * Libarchive is configured to use a buffer of 130 bytes. */

	while(1) {
		/* archive_read_data should return one of:
		 * a) 0, if there is no more data to be read,
		 * b) negative value, if there was an error,
		 * c) positive value, meaning how many bytes were read.
		 */

		bytes_read = archive_read_data(a, buf, sizeof(buf));
		assertA(bytes_read >= 0);
		if(bytes_read <= 0)
			break;

		computed_crc = crc32(computed_crc, buf, bytes_read);
	}

	assertEqualInt(computed_crc, 0x7CCA70CD);
	EPILOGUE();
}


static int64_t rar5_seek_data(struct archive_read *a, int64_t offset,
    int whence)
{
	(void) a;
	(void) offset;
	(void) whence;

	/* We're a streaming unpacker, and we don't support seeking. */

	return ARCHIVE_FATAL;
}


DEFINE_TEST(test_read_format_rar5_owner)
{
	const int DATA_SIZE = 5;
	uint8_t buff[5];

	PROLOGUE("test_read_format_rar5_owner.rar");

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("root.txt", archive_entry_pathname(ae));
	assertEqualString("root", archive_entry_uname(ae));
	assertEqualString("wheel", archive_entry_gname(ae));
	assertA((int) archive_entry_mtime(ae) > 0);
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("nobody.txt", archive_entry_pathname(ae));
	assertEqualString("nobody", archive_entry_uname(ae));
	assertEqualString("nogroup", archive_entry_gname(ae));
	assertA((int) archive_entry_mtime(ae) > 0);
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("numeric.txt", archive_entry_pathname(ae));
	assertEqualInt(9999, archive_entry_uid(ae));
	assertEqualInt(8888, archive_entry_gid(ae));
	assertA((int) archive_entry_mtime(ae) > 0);
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));

	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));

	EPILOGUE();
}


static int verify_global_checksums(struct archive_read* a) {
	return verify_checksums(a);
}


DEFINE_TEST(test_read_format_rar5_hardlink)
{
	const int DATA_SIZE = 5;
	uint8_t buff[5];

	PROLOGUE("test_read_format_rar5_hardlink.rar");

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("file.txt", archive_entry_pathname(ae));
	assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
	assertA((int) archive_entry_mtime(ae) > 0);
	assertEqualInt(DATA_SIZE, archive_entry_size(ae));
	assertA(DATA_SIZE == archive_read_data(a, buff, DATA_SIZE));

	assertA(0 == archive_read_next_header(a, &ae));
	assertEqualString("hardlink.txt", archive_entry_pathname(ae));
	assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
	assertEqualString("file.txt", archive_entry_hardlink(ae));
	assertA(0 == archive_read_data(a, NULL, archive_entry_size(ae)));

	assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae));

	EPILOGUE();
}


static int read_var_sized(struct archive_read* a, size_t* pvalue,
    size_t* pvalue_len)
{
	uint64_t v;
	uint64_t v_size = 0;

	const int ret = pvalue_len ? read_var(a, &v, &v_size)
				   : read_var(a, &v, NULL);

	if(ret == 1 && pvalue) {
		*pvalue = (size_t) v;
	}

	if(pvalue_len) {
		/* Possible data truncation should be safe. */
		*pvalue_len = (size_t) v_size;
	}

	return ret;
}


int verify_data(const uint8_t* data_ptr, int magic, int size) {
	int i = 0;

	/* This is how the test data inside test files was generated;
	 * we are re-generating it here and we check if our re-generated
	 * test data is the same as in the test file. If this test is
	 * failing it's either because there's a bug in the test case,
	 * or the unpacked data is corrupted. */

	for(i = 0; i < size / 4; ++i) {
		const int k = i + 1;
		const signed int* lptr = (const signed int*) &data_ptr[i * 4];
		signed int val = k * k - 3 * k + (1 + magic);

		if(val < 0)
			val = 0;

		/* *lptr is a value inside unpacked test file, val is the
		 * value that should be in the unpacked test file. */

		if(archive_le32dec(lptr) != (uint32_t) val)
			return 0;
	}

	return 1;
}


static int dist_cache_touch(struct rar5* rar, int idx) {
	int* q = rar->cstate.dist_cache;
	int i, dist = q[idx];

	for(i = idx; i > 0; i--)
		q[i] = q[i - 1];

	q[0] = dist;
	return dist;
}


static int run_arm_filter(struct rar5* rar, struct filter_info* flt) {
	ssize_t i = 0;
	uint32_t offset;

	circular_memcpy(rar->cstate.filtered_buf,
	    rar->cstate.window_buf, rar->cstate.window_mask,
	    rar->cstate.solid_offset + flt->block_start,
	    rar->cstate.solid_offset + flt->block_start + flt->block_length);

	for(i = 0; i < flt->block_length - 3; i += 4) {
		uint8_t* b = &rar->cstate.window_buf[
		    (rar->cstate.solid_offset +
		    flt->block_start + i + 3) & rar->cstate.window_mask];

		if(*b == 0xEB) {
			/* 0xEB = ARM's BL (branch + link) instruction. */
			offset = read_filter_data(rar,
			    (rar->cstate.solid_offset + flt->block_start + i) &
			     rar->cstate.window_mask) & 0x00ffffff;

			offset -= (uint32_t) ((i + flt->block_start) / 4);
			offset = (offset & 0x00ffffff) | 0xeb000000;
			write_filter_data(rar, (uint32_t)i, offset);
		}
	}

	return ARCHIVE_OK;
}


Fix heap buffer overflow in tf.raw_ops.UnicodeEncode.

PiperOrigin-RevId: 371717714
Change-Id: If33443b28f158e58078f1268f6b92f2728d219e0

Status GetErrorOptions(OpKernelConstruction* ctx, ErrorOptions* out) {
  *out = ErrorOptions();

  string error_policy;
  TF_RETURN_IF_ERROR(ctx->GetAttr("errors", &error_policy));

  if (error_policy == "replace") {
    out->elide_replacement = false;
  } else if (error_policy == "ignore") {
    out->elide_replacement = true;
  } else if (error_policy == "strict") {
    out->error_on_malformatting = true;
  } else {
    return errors::InvalidArgument(
        "errors policy must be one of 'strict', 'replace', or 'ignore'");
  }

  int32 replacement_char;
  TF_RETURN_IF_ERROR(ctx->GetAttr("replacement_char", &replacement_char));

  if (replacement_char >= UCHAR_MIN_VALUE &&
      replacement_char <= UCHAR_MAX_VALUE) {
    out->subst = replacement_char;
  } else {
    return errors::InvalidArgument(
        "replacement_char out of unicode codepoint range");
  }

  if (ctx->HasAttr("replace_control_characters")) {
    TF_RETURN_IF_ERROR(ctx->GetAttr("replace_control_characters",
                                    &(out->replace_control_chars)));
  }

  return Status::OK();
}

TensorFlow is an end-to-end open source platform for machine learning. An attacker can access data outside of bounds of heap allocated array in `tf.raw_ops.UnicodeEncode`. This is because the implementation(https://github.com/tensorflow/tensorflow/blob/472c1f12ad9063405737679d4f6bd43094e1d36d/tensorflow/core/kernels/unicode_ops.cc) assumes that the `input_value`/`input_splits` pair specify a valid sparse tensor. The fix will be included in TensorFlow 2.5.0. We will also cherrypick this commit on TensorFlow 2.4.2, TensorFlow 2.3.3, TensorFlow 2.2.3 and TensorFlow 2.1.4, as these are also affected and still in supported range.


  void Compute(OpKernelContext* context) override {
    // Get inputs
    const Tensor& input_tensor = context->input(0);
    const auto input_tensor_flat = input_tensor.flat<int32>();
    const Tensor& input_splits = context->input(1);
    const auto input_splits_flat = input_splits.flat<SPLITS_TYPE>();

    // Operation will treat first argument in input_splits as if it were zero
    // regardless of its actual value since splits should begin with zero and
    // end with the length of the input values vector.
    OP_REQUIRES(
        context, input_splits_flat(0) == 0,
        errors::InvalidArgument("First value in input_splits must be zero."));
    OP_REQUIRES(context,
                input_splits_flat(input_splits_flat.size() - 1) ==
                    input_tensor_flat.size(),
                errors::InvalidArgument("Last value in input_splits must be "
                                        "equal to length of input_tensor."));
    // Since we limit to a 2-D input (flat_values of rank 1 and a single splits
    // tensor), our output dimension will be 1 with it's size equal to the
    // number of splits (outer dimension or ragged tensor).
    TensorShape output_shape({input_splits.dim_size(0) - 1});
    Tensor* output_tensor;
    OP_REQUIRES_OK(context, context->allocate_output("output", output_shape,
                                                     &output_tensor));
    auto output_tensor_flat = output_tensor->flat<tstring>();

    // Use a single index over the flattened input values tensor.
    int idx = 0;
    // Loop through our split dimension to create a new string at each split.
    for (int i = 1; i < input_splits_flat.size(); ++i) {
      icu::UnicodeString unicode_string;
      icu::UnicodeStringAppendable appendable_unicode_string(unicode_string);
      OP_REQUIRES(
          context, input_splits_flat(i - 1) <= input_splits_flat(i),
          errors::InvalidArgument(
              "Values in input_splits must be equal or in ascending order."));
      OP_REQUIRES(
          context, input_splits_flat(i) <= input_tensor_flat.size(),
          errors::InvalidArgument("Values in input_splits must be less than or "
                                  "equal to input_tensor length."));
      for (; idx < input_splits_flat(i); ++idx) {
        int32 code_point = input_tensor_flat(idx);
        // Check for invalid code point
        if (!U_IS_UNICODE_CHAR(code_point)) {
          if (error_options_.error_on_malformatting) {
            context->CtxFailure(errors::InvalidArgument(
                "Code point is out of range for Unicode, or a noncharacter."));
            return;
          } else if (!error_options_.elide_replacement) {
            code_point = error_options_.subst;
          }
        }
        appendable_unicode_string.appendCodePoint(code_point);
      }
      // Encode our string and save in the output.
      tstring result;
      Encode(encoding_, unicode_string, &result);
      output_tensor_flat(i - 1) = std::move(result);
    }
  }


Prevent segfault in `quantize_and_dequantize`

Fixes #42105.

If `tf.quantization.quantize_and_dequantize` is called with `axis` argument pointing to outside of the input tensor, we obtain a `CHECK` fail which then aborts the application/interpreter. This change adds a condition check and returns a `Status` instead of crashing.

PiperOrigin-RevId: 337972243
Change-Id: I71ec32c00a87266e364fb017f0ad5dfd3e23542f

  explicit QuantizeAndDequantizeV2Op(OpKernelConstruction* ctx)
      : OpKernel(ctx) {
    OP_REQUIRES_OK(ctx, ctx->GetAttr("signed_input", &signed_input_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("axis", &axis_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("num_bits", &num_bits_));
    OP_REQUIRES(ctx, num_bits_ > 0 && num_bits_ < (signed_input_ ? 62 : 63),
                errors::InvalidArgument("num_bits is out of range: ", num_bits_,
                                        " with signed_input_ ", signed_input_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("range_given", &range_given_));

    string round_mode_string;
    OP_REQUIRES_OK(ctx, ctx->GetAttr("round_mode", &round_mode_string));
    OP_REQUIRES(
        ctx,
        (round_mode_string == "HALF_UP" || round_mode_string == "HALF_TO_EVEN"),
        errors::InvalidArgument("Round mode string must be "
                                "'HALF_UP' or "
                                "'HALF_TO_EVEN', is '" +
                                round_mode_string + "'"));
    if (round_mode_string == "HALF_UP") {
      round_mode_ = ROUND_HALF_UP;
    } else if (round_mode_string == "HALF_TO_EVEN") {
      round_mode_ = ROUND_HALF_TO_EVEN;
    }
    OP_REQUIRES_OK(ctx, ctx->GetAttr("narrow_range", &narrow_range_));
  }

In Tensorflow before version 2.4.0, an attacker can pass an invalid `axis` value to `tf.quantization.quantize_and_dequantize`. This results in accessing a dimension outside the rank of the input tensor in the C++ kernel implementation. However, dim_size only does a DCHECK to validate the argument and then uses it to access the corresponding element of an array. Since in normal builds, `DCHECK`-like macros are no-ops, this results in segfault and access out of bounds of the array. The issue is patched in eccb7ec454e6617738554a255d77f08e60ee0808 and TensorFlow 2.4.0 will be released containing the patch. TensorFlow nightly packages after this commit will also have the issue resolved.


  void operator()(const CPUDevice& d, typename TTypes<T>::ConstVec input,
                  const bool signed_input, const int num_bits,
                  const bool range_given, Tensor* input_min_tensor,
                  Tensor* input_max_tensor, QuantizerRoundMode round_mode,
                  bool narrow_range, typename TTypes<T>::Vec out) {
    QuantizeAndDequantizeOneScaleImpl<CPUDevice, T>::Compute(
        d, input, signed_input, num_bits, range_given, input_min_tensor,
        input_max_tensor, round_mode, narrow_range, out);
  }


  void Compute(OpKernelContext* ctx) override {
    const Tensor& input = ctx->input(0);

    Tensor* output = nullptr;
    OP_REQUIRES_OK(ctx, ctx->allocate_output(0, input.shape(), &output));

    // One global scale.
    Tensor input_min_tensor(DataTypeToEnum<T>::value, TensorShape());
    Tensor input_max_tensor(DataTypeToEnum<T>::value, TensorShape());
    // Initialize the tensors with the values in the Attrs.
    input_min_tensor.template scalar<T>()() = static_cast<T>(input_min_);
    input_max_tensor.template scalar<T>()() = static_cast<T>(input_max_);

    functor::QuantizeAndDequantizeOneScaleFunctor<Device, T> functor;
    functor(ctx->eigen_device<Device>(), input.flat<T>(), signed_input_,
            num_bits_, range_given_, &input_min_tensor, &input_max_tensor,
            ROUND_HALF_TO_EVEN, /*narrow_range=*/false, output->flat<T>());
  }


  void operator()(const CPUDevice& d, typename TTypes<T, 3>::ConstTensor input,
                  bool signed_input, int num_bits, bool range_given,
                  Tensor* input_min_tensor, Tensor* input_max_tensor,
                  QuantizerRoundMode round_mode, bool narrow_range,
                  typename TTypes<T, 3>::Tensor out) {
    QuantizeAndDequantizePerChannelImpl<CPUDevice, T>::Compute(
        d, input, signed_input, num_bits, range_given, input_min_tensor,
        input_max_tensor, round_mode, narrow_range, out);
  }


  void operator()(const CPUDevice& d, typename TTypes<T>::ConstFlat gradient,
                  typename TTypes<T>::ConstFlat input,
                  typename TTypes<T>::ConstScalar input_min_tensor,
                  typename TTypes<T>::ConstScalar input_max_tensor,
                  typename TTypes<T>::Flat input_backprop,
                  typename TTypes<T>::Scalar input_min_backprop,
                  typename TTypes<T>::Scalar input_max_backprop) {
    QuantizeAndDequantizeOneScaleGradientImpl<CPUDevice, T>::Compute(
        d, gradient, input, input_min_tensor, input_max_tensor, input_backprop,
        input_min_backprop, input_max_backprop);
  }


  explicit QuantizeAndDequantizeOp(OpKernelConstruction* ctx) : OpKernel(ctx) {
    OP_REQUIRES_OK(ctx, ctx->GetAttr("signed_input", &signed_input_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("num_bits", &num_bits_));
    OP_REQUIRES(ctx, num_bits_ > 0 && num_bits_ < (signed_input_ ? 62 : 63),
                errors::InvalidArgument("num_bits is out of range: ", num_bits_,
                                        " with signed_input_ ", signed_input_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("range_given", &range_given_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("input_min", &input_min_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("input_max", &input_max_));
    if (range_given_) {
      OP_REQUIRES(
          ctx, input_min_ <= input_max_,
          errors::InvalidArgument("Invalid range: input_min ", input_min_,
                                  " > input_max ", input_max_));
    }
  }


  void operator()(const CPUDevice& d,
                  typename TTypes<T, 3>::ConstTensor gradient,
                  typename TTypes<T, 3>::ConstTensor input,
                  const Tensor* input_min_tensor,
                  const Tensor* input_max_tensor,
                  typename TTypes<T, 3>::Tensor input_backprop,
                  typename TTypes<T>::Flat input_min_backprop,
                  typename TTypes<T>::Flat input_max_backprop) {
    QuantizeAndDequantizePerChannelGradientImpl<CPUDevice, T>::Compute(
        d, gradient, input, input_min_tensor, input_max_tensor, input_backprop,
        input_min_backprop, input_max_backprop);
  }


  explicit QuantizeAndDequantizeV3Op(OpKernelConstruction* ctx)
      : OpKernel(ctx) {
    OP_REQUIRES_OK(ctx, ctx->GetAttr("signed_input", &signed_input_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("range_given", &range_given_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("narrow_range", &narrow_range_));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("axis", &axis_));
  }


  explicit QuantizeAndDequantizeV4GradientOp(OpKernelConstruction* ctx)
      : OpKernel::OpKernel(ctx) {
    OP_REQUIRES_OK(ctx, ctx->GetAttr("axis", &axis_));
  }


Fix heap buffer overflow

PiperOrigin-RevId: 372132844
Change-Id: Idef9895efaf145f2b1c23d31983601ec980cd5e4

  explicit MaxPoolingGradOp(OpKernelConstruction* context) : OpKernel(context) {
    string data_format;
    OP_REQUIRES_OK(context, context->GetAttr("data_format", &data_format));
    OP_REQUIRES(context, FormatFromString(data_format, &data_format_),
                errors::InvalidArgument("Invalid data format"));
    if (context->num_inputs() == 3) {
      OP_REQUIRES_OK(context, context->GetAttr("ksize", &ksize_));
      OP_REQUIRES(context, ksize_.size() == 4,
                  errors::InvalidArgument("Sliding window ksize field must "
                                          "specify 4 dimensions"));
      OP_REQUIRES_OK(context, context->GetAttr("strides", &stride_));
      OP_REQUIRES(context, stride_.size() == 4,
                  errors::InvalidArgument("Sliding window strides field must "
                                          "specify 4 dimensions"));
      const int32 ksize_n = GetTensorDim(ksize_, data_format_, 'N');
      const int32 stride_n = GetTensorDim(stride_, data_format_, 'N');
      OP_REQUIRES(context, ksize_n == 1 && stride_n == 1,
                  errors::Unimplemented(
                      "Pooling is not yet supported on the batch dimension."));
    }
    OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_));
    if (padding_ == Padding::EXPLICIT) {
      OP_REQUIRES_OK(
          context, context->GetAttr("explicit_paddings", &explicit_paddings_));
      OP_REQUIRES_OK(context, CheckValidPadding(padding_, explicit_paddings_,
                                                /*num_dims=*/4, data_format_));
    }
    TF_CHECK_OK(ReadBoolFromEnvVar("TF_ENABLE_MAXPOOL_NANPROP", false,
                                   &propagate_nans_));
  }

TensorFlow is an end-to-end open source platform for machine learning. The implementation of `tf.raw_ops.MaxPoolGradWithArgmax` can cause reads outside of bounds of heap allocated data if attacker supplies specially crafted inputs. The implementation(https://github.com/tensorflow/tensorflow/blob/ef0c008ee84bad91ec6725ddc42091e19a30cf0e/tensorflow/core/kernels/maxpooling_op.cc#L1016-L1017) uses the same value to index in two different arrays but there is no guarantee that the sizes are identical. The fix will be included in TensorFlow 2.5.0. We will also cherrypick this commit on TensorFlow 2.4.2, TensorFlow 2.3.3, TensorFlow 2.2.3 and TensorFlow 2.1.4, as these are also affected and still in supported range.


  void Compute(OpKernelContext* context) override {
    const Tensor& tensor_in = context->input(0);

    std::vector<int32> ksize = ksize_;
    std::vector<int32> stride = stride_;

    if (context->num_inputs() != 1) {
      const Tensor& tensor_ksize = context->input(1);
      auto value_ksize = tensor_ksize.flat<int32>();
      ksize.resize(tensor_ksize.shape().num_elements());
      std::copy_n(&value_ksize(0), ksize.size(), ksize.begin());

      const Tensor& tensor_stride = context->input(2);
      auto value_stride = tensor_stride.flat<int32>();
      stride.resize(tensor_stride.shape().num_elements());
      std::copy_n(&value_stride(0), stride.size(), stride.begin());
    }
    OP_REQUIRES(context, ksize.size() == 4,
                errors::InvalidArgument("Sliding window ksize field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES(context, stride.size() == 4,
                errors::InvalidArgument("Sliding window stride field must "
                                        "specify 4 dimensions"));
    const int32 ksize_n = GetTensorDim(ksize, data_format_, 'N');
    const int32 stride_n = GetTensorDim(stride, data_format_, 'N');
    OP_REQUIRES(context, ksize_n == 1 && stride_n == 1,
                errors::Unimplemented(
                    "Pooling is not yet supported on the batch dimension."));

    PoolParameters params{context,
                          ksize,
                          stride,
                          padding_,
                          /*explicit_paddings=*/{},
                          data_format_,
                          tensor_in.shape()};
    if (!context->status().ok()) {
      return;
    }

    TensorShape out_shape =
        ShapeFromFormat(data_format_, params.tensor_in_batch, params.out_height,
                        params.out_width, params.depth);
    if (data_format_ == FORMAT_NCHW) {
      DnnPoolingOp<T>::Compute(context, se::dnn::PoolingMode::kMaximum, ksize,
                               stride, padding_, explicit_paddings_,
                               data_format_, tensor_in, out_shape,
                               propagate_nans_);
    } else {
      CHECK(data_format_ == FORMAT_NHWC)
          << "MaxPool only supports NCHW or NHWC format";
      Tensor* output = nullptr;
      OP_REQUIRES_OK(context, context->allocate_output(0, out_shape, &output));
      LaunchMaxPoolingNoMask<Device, T>::launch(context, params, tensor_in,
                                                output, propagate_nans_);
    }
  }


  static void launch(OpKernelContext* context, const PoolParameters& params,
                     const Tensor& grad_in, const Tensor& argmax,
                     Tensor* grad_out, const bool include_batch_in_index) {
    const int input_size = params.tensor_in_batch * params.tensor_in_rows *
                           params.tensor_in_cols * params.depth;
    const int output_size = params.tensor_in_batch * params.out_height *
                            params.out_width * params.depth;
    const int top_offset =
        params.tensor_in_rows * params.tensor_in_cols * params.depth;
    const int bottom_offset =
        params.out_width * params.out_height * params.depth;
    bool status = functor::MaxPoolGradBackwardWithArgmax<T>()(
        output_size, input_size, grad_in.flat<T>().data(),
        reinterpret_cast<const int64*>(argmax.flat<int64>().data()), top_offset,
        bottom_offset, grad_out->flat<T>().data(), context->eigen_gpu_device(),
        include_batch_in_index);
    if (!status) {
      context->SetStatus(
          errors::Internal("Failed launching MaxPoolGradBackwardWithArgmax"));
    }
  }


  static void launch(OpKernelContext* context, const PoolParameters& params,
                     const Tensor& grad_in, const Tensor& argmax,
                     Tensor* grad_out, const bool include_batch_in_index) {
    const DeviceBase::CpuWorkerThreads& worker_threads =
        *(context->device()->tensorflow_cpu_worker_threads());

    auto shard = [&grad_in, &argmax, &grad_out, include_batch_in_index](
                     int64 start, int64 limit) {
      const int64 batch_size =
          GetTensorDim(grad_out->shape(), FORMAT_NHWC, 'N');
      const int64 output_size_per_batch = grad_out->NumElements() / batch_size;
      const int64 input_size_per_batch = grad_in.NumElements() / batch_size;

      {
        auto grad_out_flat = grad_out->flat<T>();
        auto argmax_flat = argmax.flat<int64>();
        auto grad_in_flat = grad_in.flat<T>();

        const int64 output_start = start * output_size_per_batch;
        const int64 output_end = limit * output_size_per_batch;
        EigenMatrixMap inputShard(grad_out_flat.data() + output_start, 1,
                                  output_end - output_start);
        inputShard.setConstant(T(0));

        const int input_start = start * input_size_per_batch;
        const int input_end = limit * input_size_per_batch;
        for (int64 index = input_start; index < input_end; index++) {
          if (index >= argmax.NumElements()) {
            break;
          }
          int64 grad_out_index = argmax_flat(index);
          if (!include_batch_in_index) {
            const int64 cur_batch = index / input_size_per_batch;
            grad_out_index += cur_batch * output_size_per_batch;
          }
          CHECK(grad_out_index >= output_start && grad_out_index < output_end)
              << "Invalid output gradient index: " << grad_out_index << ", "
              << output_start << ", " << output_end;
          grad_out_flat(grad_out_index) += grad_in_flat(index);
        }
      }
    };

    const int64 batch_size = GetTensorDim(grad_out->shape(), FORMAT_NHWC, 'N');
    const int64 shard_cost = grad_out->NumElements() / batch_size;
    Shard(worker_threads.num_threads, worker_threads.workers, batch_size,
          shard_cost, shard);
  }


  explicit MaxPoolingNoMaskV2Op(OpKernelConstruction* context)
      : OpKernel(context) {
    string data_format;
    OP_REQUIRES_OK(context, context->GetAttr("data_format", &data_format));
    OP_REQUIRES(context, FormatFromString(data_format, &data_format_),
                errors::InvalidArgument("Invalid data format"));
    if (context->num_inputs() == 1) {
      OP_REQUIRES_OK(context, context->GetAttr("ksize", &ksize_));
      OP_REQUIRES(context, ksize_.size() == 4,
                  errors::InvalidArgument("Sliding window ksize field must "
                                          "specify 4 dimensions"));
      OP_REQUIRES_OK(context, context->GetAttr("strides", &stride_));
      OP_REQUIRES(context, stride_.size() == 4,
                  errors::InvalidArgument("Sliding window stride field must "
                                          "specify 4 dimensions"));
      const int32 ksize_n = GetTensorDim(ksize_, data_format_, 'N');
      const int32 stride_n = GetTensorDim(stride_, data_format_, 'N');
      OP_REQUIRES(context, ksize_n == 1 && stride_n == 1,
                  errors::Unimplemented(
                      "Pooling is not yet supported on the batch dimension."));
    }
    OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_));
    TF_CHECK_OK(ReadBoolFromEnvVar("TF_ENABLE_MAXPOOL_NANPROP", false,
                                   &propagate_nans_));
  }


  explicit MaxPoolingNoMaskOp(OpKernelConstruction* context)
      : OpKernel(context) {
    string data_format;
    OP_REQUIRES_OK(context, context->GetAttr("data_format", &data_format));
    OP_REQUIRES(context, FormatFromString(data_format, &data_format_),
                errors::InvalidArgument("Invalid data format"));
    OP_REQUIRES_OK(context, context->GetAttr("ksize", &ksize_));
    OP_REQUIRES(context, ksize_.size() == 4,
                errors::InvalidArgument("Sliding window ksize field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES_OK(context, context->GetAttr("strides", &stride_));
    OP_REQUIRES(context, stride_.size() == 4,
                errors::InvalidArgument("Sliding window stride field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_));
    OP_REQUIRES_OK(context,
                   context->GetAttr("explicit_paddings", &explicit_paddings_));
    const int32 ksize_n = GetTensorDim(ksize_, data_format_, 'N');
    const int32 stride_n = GetTensorDim(stride_, data_format_, 'N');
    OP_REQUIRES(context, ksize_n == 1 && stride_n == 1,
                errors::Unimplemented(
                    "Pooling is not yet supported on the batch dimension."));

    TF_CHECK_OK(ReadBoolFromEnvVar("TF_ENABLE_MAXPOOL_NANPROP", false,
                                   &propagate_nans_));
  }


  void Compute(OpKernelContext* context) override {
    const Tensor& tensor_in = context->input(0);
    const Tensor& tensor_out = context->input(1);
    const Tensor& out_backprop = context->input(2);

    // For maxpooling, tensor_in should have 4 dimensions.
    OP_REQUIRES(context, tensor_in.dims() == 4,
                errors::InvalidArgument("tensor_in must be 4-dimensional"));
    OP_REQUIRES(context, tensor_out.dims() == 4,
                errors::InvalidArgument("tensor_out must be 4-dimensional"));
    // For maxpooling, out_backprop should have 4 dimensions.
    OP_REQUIRES(context, out_backprop.dims() == 4,
                errors::InvalidArgument("out_backprop must be 4-dimensional"));

    const TensorShape& output_shape = tensor_in.shape();

    Tensor tensor_out_dup;
    OP_REQUIRES_OK(context, context->forward_input_or_allocate_temp(
                                {1}, DataTypeToEnum<T>::v(), tensor_out.shape(),
                                &tensor_out_dup));
    Tensor tensor_out_arg_max;
    OP_REQUIRES_OK(context, context->allocate_temp(DataTypeToEnum<int64>::v(),
                                                   tensor_out.shape(),
                                                   &tensor_out_arg_max));
    std::vector<int32> ksize = ksize_;
    std::vector<int32> stride = stride_;
    if (context->num_inputs() == 5) {
      const Tensor& tensor_ksize = context->input(3);
      auto value_ksize = tensor_ksize.flat<int32>();
      ksize.resize(tensor_ksize.shape().num_elements());
      std::copy_n(&value_ksize(0), ksize.size(), ksize.begin());

      const Tensor& tensor_stride = context->input(4);
      auto value_stride = tensor_stride.flat<int32>();
      stride.resize(tensor_stride.shape().num_elements());
      std::copy_n(&value_stride(0), stride.size(), stride.begin());
    }

    OP_REQUIRES(context, ksize.size() == 4,
                errors::InvalidArgument("Sliding window ksize field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES(context, stride.size() == 4,
                errors::InvalidArgument("Sliding window strides field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES(context, ksize[0] == 1 && stride[0] == 1,
                errors::Unimplemented(
                    "Pooling is not yet supported on the batch dimension."));
    OP_REQUIRES(
        context, ksize[3] == 1 && stride[3] == 1,
        errors::Unimplemented(
            "MaxPoolingGrad is not yet supported on the depth dimension."));

    PoolParameters params{context,
                          ksize,
                          stride,
                          padding_,
                          explicit_paddings_,
                          FORMAT_NHWC,
                          tensor_in.shape()};
    if (!context->status().ok()) {
      return;
    }

    Tensor* output = nullptr;
    OP_REQUIRES_OK(context, context->forward_input_or_allocate_output(
                                {0}, 0, output_shape, &output));

    SpatialMaxPoolWithArgMaxHelper<CPUDevice, T, int64>(
        context, &tensor_out_dup, &tensor_out_arg_max, output, tensor_in,
        out_backprop, params, true);
  }


  explicit MaxPoolingGradWithArgmaxOp(OpKernelConstruction* context)
      : OpKernel(context) {
    string data_format_str;
    auto status = context->GetAttr("data_format", &data_format_str);
    if (status.ok()) {
      OP_REQUIRES(context, FormatFromString(data_format_str, &data_format_),
                  errors::InvalidArgument("Invalid data format"));
    }

    OP_REQUIRES_OK(context, context->GetAttr("ksize", &ksize_));
    OP_REQUIRES(context, ksize_.size() == 4,
                errors::InvalidArgument("Sliding window ksize field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES_OK(context, context->GetAttr("strides", &stride_));
    OP_REQUIRES(context, stride_.size() == 4,
                errors::InvalidArgument("Sliding window stride field must "
                                        "specify 4 dimensions"));
    OP_REQUIRES_OK(context, context->GetAttr("padding", &padding_));
    OP_REQUIRES(context, ksize_[0] == 1 && stride_[0] == 1,
                errors::Unimplemented(
                    "Pooling is not yet supported on the batch dimension."));
    OP_REQUIRES_OK(context, context->GetAttr("include_batch_in_index",
                                             &include_batch_in_index_));
  }


  void SpatialMaxPoolGradGrad(OpKernelContext* context, Tensor* bottom_diff,
                              const Tensor& tensor_in, const Tensor& tensor_out,
                              const Tensor& top_diff,
                              const PoolParameters& params,
                              const Padding& padding) {
    typedef Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>>
        ConstEigenMatrixMap;
    typedef Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>>
        EigenMatrixMap;

    ConstEigenMatrixMap in_mat(
        tensor_in.flat<T>().data(), params.depth,
        params.tensor_in_cols * params.tensor_in_rows * params.tensor_in_batch);
    ConstEigenMatrixMap out_mat(
        tensor_out.flat<T>().data(), params.depth,
        params.out_width * params.out_height * params.tensor_in_batch);
    ConstEigenMatrixMap top_diff_mat(
        top_diff.flat<T>().data(), params.depth,
        params.tensor_in_cols * params.tensor_in_rows * params.tensor_in_batch);
    EigenMatrixMap bottom_diff_mat(
        bottom_diff->flat<T>().data(), params.depth,
        params.out_width * params.out_height * params.tensor_in_batch);

    const DeviceBase::CpuWorkerThreads& worker_threads =
        *(context->device()->tensorflow_cpu_worker_threads());

    // The following code basically does the following:
    // 1. Flattens the input, output, top_diff and bottom_diff tensors into
    //    two dimensional arrays.
    //    tensor_in_as_matrix:
    //      depth by (tensor_in_cols * tensor_in_rows * tensor_in_batch)
    //    tensor_out_as_matrix:
    //      depth by (out_width * out_height * tensor_in_batch)
    //    top_diff_as_matrix:
    //      depth by (tensor_in_cols * tensor_in_rows * tensor_in_batch)
    //    bottom_diff_as_matrix:
    //      depth by (out_width * out_height * tensor_in_batch)
    //
    // 2. Walks through the set of columns in the flattened
    //    tensor_in_as_matrix, tensor_out_as_matrix, top_diff_as_matrix
    //    and updates the column(s) corresponding to the maximum values in
    //    tensor_out_as_matrix with the corresponding values in
    //    top_diff_as_matrix.
    auto shard = [&params, &in_mat, &out_mat, &top_diff_mat, &bottom_diff_mat](
                     int64 start, int64 limit) {
      const int32 depth = params.depth;
      const int32 in_rows = params.tensor_in_rows;
      const int32 in_cols = params.tensor_in_cols;
      const int32 pad_top = params.pad_top;
      const int32 pad_left = params.pad_left;
      const int32 window_rows = params.window_rows;
      const int32 window_cols = params.window_cols;
      const int32 row_stride = params.row_stride;
      const int32 col_stride = params.col_stride;
      const int32 out_height = params.out_height;
      const int32 out_width = params.out_width;

      {
        // Initializes the output grad backprop tensor with 0.
        const int32 output_image_size = out_height * out_width * params.depth;
        EigenMatrixMap bottom_diff_shard(
            bottom_diff_mat.data() + start * output_image_size, 1,
            (limit - start) * output_image_size);
        bottom_diff_shard.setZero();
      }

      for (int b = start; b < limit; ++b) {
        for (int ph = 0; ph < out_height; ++ph) {
          for (int pw = 0; pw < out_width; ++pw) {
            // (h_start, h_end) * (w_start, w_end) is the range that the input
            // vector projects to.
            int h_start = ph * row_stride - pad_top;
            const int h_end = std::min(h_start + window_rows, in_rows);
            int w_start = pw * col_stride - pad_left;
            const int w_end = std::min(w_start + window_cols, in_cols);
            h_start = std::max(h_start, 0);
            w_start = std::max(w_start, 0);
            const int out_index = (b * out_height + ph) * out_width + pw;
            // Find value corresponding to the input maximum in top_diff.
            for (int d = 0; d < depth; ++d) {
              const T& output_ref = out_mat.coeffRef(d, out_index);
              bool should_stop = false;
              for (int h = h_start; h < h_end && !should_stop; ++h) {
                for (int w = w_start; w < w_end && !should_stop; ++w) {
                  const int in_index = (b * in_rows + h) * in_cols + w;
                  const T& input_ref = in_mat.coeffRef(d, in_index);
                  if (output_ref == input_ref) {
                    T& bottom_diff_ref = bottom_diff_mat.coeffRef(d, out_index);
                    bottom_diff_ref = top_diff_mat.coeffRef(d, in_index);
                    should_stop = true;
                  }
                }
              }
            }
          }
        }
      }
    };

    const int64 shard_cost = params.out_width * params.out_height *
                             params.depth * params.window_rows *
                             params.window_cols;
    Shard(worker_threads.num_threads, worker_threads.workers,
          params.tensor_in_batch, shard_cost, shard);
  }


  static void launch(OpKernelContext* context, const PoolParameters& params,
                     const Tensor& input, Tensor* output, Tensor* argmax,
                     bool propagate_nans, bool include_batch_in_index) {
    bool status = functor::MaxPoolForwardWithOptionalArgmax<T>()(
        input.flat<T>().data(), params.tensor_in_batch, params.tensor_in_rows,
        params.tensor_in_cols, params.depth, params.out_height,
        params.out_width, params.window_rows, params.window_cols,
        params.row_stride, params.col_stride, params.pad_top, params.pad_left,
        output->flat<T>().data(),
        reinterpret_cast<int64*>(argmax->flat<int64>().data()),
        context->eigen_gpu_device(), propagate_nans, include_batch_in_index);
    if (!status) {
      context->SetStatus(
          errors::Internal("Failed launching MaxPoolForwardWithArgmax"));
    }
  }