static int sqfs_read_directory_table(unsigned char **dir_table, u32 **pos_list)
{
	u64 start, n_blks, table_offset, table_size;
	struct squashfs_super_block *sblk = ctxt.sblk;
	int j, ret = 0, metablks_count = -1;
	unsigned char *src_table, *dtb;
	u32 src_len, dest_offset = 0;
	unsigned long dest_len = 0;
	bool compressed;

	*dir_table = NULL;
	*pos_list = NULL;
	/* DIRECTORY TABLE */
	table_size = get_unaligned_le64(&sblk->fragment_table_start) -
		get_unaligned_le64(&sblk->directory_table_start);
	start = get_unaligned_le64(&sblk->directory_table_start) /
		ctxt.cur_dev->blksz;
	n_blks = sqfs_calc_n_blks(sblk->directory_table_start,
				  sblk->fragment_table_start, &table_offset);

	/* Allocate a proper sized buffer (dtb) to store the directory table */
	dtb = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
	if (!dtb)
		return -ENOMEM;

	if (sqfs_disk_read(start, n_blks, dtb) < 0)
		goto out;

	/* Parse directory table (metadata block) header */
	ret = sqfs_read_metablock(dtb, table_offset, &compressed, &src_len);
	if (ret)
		goto out;

	/* Calculate total size to store the whole decompressed table */
	metablks_count = sqfs_count_metablks(dtb, table_offset, table_size);
	if (metablks_count < 1)
		goto out;

	*dir_table = malloc(metablks_count * SQFS_METADATA_BLOCK_SIZE);
	if (!*dir_table)
		goto out;

	*pos_list = malloc(metablks_count * sizeof(u32));
	if (!*pos_list)
		goto out;

	ret = sqfs_get_metablk_pos(*pos_list, dtb, table_offset,
				   metablks_count);
	if (ret) {
		metablks_count = -1;
		goto out;
	}

	src_table = dtb + table_offset + SQFS_HEADER_SIZE;

	/* Extract compressed Directory table */
	dest_offset = 0;
	for (j = 0; j < metablks_count; j++) {
		sqfs_read_metablock(dtb, table_offset, &compressed, &src_len);
		if (compressed) {
			dest_len = SQFS_METADATA_BLOCK_SIZE;
			ret = sqfs_decompress(&ctxt, *dir_table +
					      (j * SQFS_METADATA_BLOCK_SIZE),
					      &dest_len, src_table, src_len);
			if (ret) {
				metablks_count = -1;
				goto out;
			}

			if (dest_len < SQFS_METADATA_BLOCK_SIZE) {
				dest_offset += dest_len;
				break;
			}

			dest_offset += dest_len;
		} else {
			memcpy(*dir_table + (j * SQFS_METADATA_BLOCK_SIZE),
			       src_table, src_len);
		}

		/*
		 * Offsets to the decompression destination, to the metadata
		 * buffer 'dtb' and to the decompression source, respectively.
		 */
		table_offset += src_len + SQFS_HEADER_SIZE;
		src_table += src_len + SQFS_HEADER_SIZE;
	}

out:
	if (metablks_count < 1) {
		free(*dir_table);
		free(*pos_list);
		*dir_table = NULL;
		*pos_list = NULL;
	}
	free(dtb);

	return metablks_count;
}


static char *sqfs_basename(char *path)
{
	char *fname;

	fname = path + strlen(path) - 1;
	while (fname >= path) {
		if (*fname == '/') {
			fname++;
			break;
		}

		fname--;
	}

	return fname;
}

fs/squashfs: sqfs_read: Prevent arbitrary code execution

Following Jincheng's report, an out-of-band write leading to arbitrary
code execution is possible because on one side the squashfs logic
accepts directory names up to 65535 bytes (u16), while U-Boot fs logic
accepts directory names up to 255 bytes long.

Prevent such an exploit from happening by capping directory name sizes
to 255. Use a define for this purpose so that developers can link the
limitation to its source and eventually kill it some day by dynamically
allocating this array (if ever desired).

Link: https://lore.kernel.org/all/CALO=DHFB+yBoXxVr5KcsK0iFdg+e7ywko4-e+72kjbcS8JBfPw@mail.gmail.com
Reported-by: Jincheng Wang <jc.w4ng@gmail.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Tested-by: Jincheng Wang <jc.w4ng@gmail.com>


static int sqfs_get_regfile_info(struct squashfs_reg_inode *reg,
				 struct squashfs_file_info *finfo,
				 struct squashfs_fragment_block_entry *fentry,
				 __le32 blksz)
{
	int datablk_count = 0, ret;

	finfo->size = get_unaligned_le32(&reg->file_size);
	finfo->offset = get_unaligned_le32(&reg->offset);
	finfo->start = get_unaligned_le32(&reg->start_block);
	finfo->frag = SQFS_IS_FRAGMENTED(get_unaligned_le32(&reg->fragment));

	if (finfo->frag && finfo->offset == 0xFFFFFFFF)
		return -EINVAL;

	if (finfo->size < 1 || finfo->start == 0xFFFFFFFF)
		return -EINVAL;

	if (finfo->frag) {
		datablk_count = finfo->size / le32_to_cpu(blksz);
		ret = sqfs_frag_lookup(get_unaligned_le32(&reg->fragment),
				       fentry);
		if (ret < 0)
			return -EINVAL;
		finfo->comp = ret;
		if (fentry->size < 1 || fentry->start == 0x7FFFFFFF)
			return -EINVAL;
	} else {
		datablk_count = DIV_ROUND_UP(finfo->size, le32_to_cpu(blksz));
	}

	finfo->blk_sizes = malloc(datablk_count * sizeof(u32));
	if (!finfo->blk_sizes)
		return -ENOMEM;

	return datablk_count;
}


static int sqfs_frag_lookup(u32 inode_fragment_index,
			    struct squashfs_fragment_block_entry *e)
{
	u64 start, n_blks, src_len, table_offset, start_block;
	unsigned char *metadata_buffer, *metadata, *table;
	struct squashfs_fragment_block_entry *entries;
	struct squashfs_super_block *sblk = ctxt.sblk;
	unsigned long dest_len;
	int block, offset, ret;
	u16 header;

	metadata_buffer = NULL;
	entries = NULL;
	table = NULL;

	if (inode_fragment_index >= get_unaligned_le32(&sblk->fragments))
		return -EINVAL;

	start = get_unaligned_le64(&sblk->fragment_table_start) /
		ctxt.cur_dev->blksz;
	n_blks = sqfs_calc_n_blks(sblk->fragment_table_start,
				  sblk->export_table_start,
				  &table_offset);

	/* Allocate a proper sized buffer to store the fragment index table */
	table = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
	if (!table) {
		ret = -ENOMEM;
		goto out;
	}

	if (sqfs_disk_read(start, n_blks, table) < 0) {
		ret = -EINVAL;
		goto out;
	}

	block = SQFS_FRAGMENT_INDEX(inode_fragment_index);
	offset = SQFS_FRAGMENT_INDEX_OFFSET(inode_fragment_index);

	/*
	 * Get the start offset of the metadata block that contains the right
	 * fragment block entry
	 */
	start_block = get_unaligned_le64(table + table_offset + block *
					 sizeof(u64));

	start = start_block / ctxt.cur_dev->blksz;
	n_blks = sqfs_calc_n_blks(cpu_to_le64(start_block),
				  sblk->fragment_table_start, &table_offset);

	metadata_buffer = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
	if (!metadata_buffer) {
		ret = -ENOMEM;
		goto out;
	}

	if (sqfs_disk_read(start, n_blks, metadata_buffer) < 0) {
		ret = -EINVAL;
		goto out;
	}

	/* Every metadata block starts with a 16-bit header */
	header = get_unaligned_le16(metadata_buffer + table_offset);
	metadata = metadata_buffer + table_offset + SQFS_HEADER_SIZE;

	if (!metadata || !header) {
		ret = -ENOMEM;
		goto out;
	}

	entries = malloc(SQFS_METADATA_BLOCK_SIZE);
	if (!entries) {
		ret = -ENOMEM;
		goto out;
	}

	if (SQFS_COMPRESSED_METADATA(header)) {
		src_len = SQFS_METADATA_SIZE(header);
		dest_len = SQFS_METADATA_BLOCK_SIZE;
		ret = sqfs_decompress(&ctxt, entries, &dest_len, metadata,
				      src_len);
		if (ret) {
			ret = -EINVAL;
			goto out;
		}
	} else {
		memcpy(entries, metadata, SQFS_METADATA_SIZE(header));
	}

	*e = entries[offset];
	ret = SQFS_COMPRESSED_BLOCK(e->size);

out:
	free(entries);
	free(metadata_buffer);
	free(table);

	return ret;
}


static int sqfs_count_metablks(void *table, u32 offset, int table_size)
{
	int count = 0, cur_size = 0, ret;
	u32 data_size;
	bool comp;

	do {
		ret = sqfs_read_metablock(table, offset + cur_size, &comp,
					  &data_size);
		if (ret)
			return -EINVAL;
		cur_size += data_size + SQFS_HEADER_SIZE;
		count++;
	} while (cur_size < table_size);

	return count;
}


static int sqfs_disk_read(__u32 block, __u32 nr_blocks, void *buf)
{
	ulong ret;

	if (!ctxt.cur_dev)
		return -1;

	ret = blk_dread(ctxt.cur_dev, ctxt.cur_part_info.start + block,
			nr_blocks, buf);

	if (ret != nr_blocks)
		return -1;

	return ret;
}


static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
			   int token_count, u32 *m_list, int m_count)
{
	struct squashfs_super_block *sblk = ctxt.sblk;
	char *path, *target, **sym_tokens, *res, *rem;
	int j, ret = 0, new_inode_number, offset;
	struct squashfs_symlink_inode *sym;
	struct squashfs_ldir_inode *ldir;
	struct squashfs_dir_inode *dir;
	struct fs_dir_stream *dirsp;
	struct fs_dirent *dent;
	unsigned char *table;

	res = NULL;
	rem = NULL;
	path = NULL;
	target = NULL;
	sym_tokens = NULL;

	dirsp = (struct fs_dir_stream *)dirs;

	/* Start by root inode */
	table = sqfs_find_inode(dirs->inode_table, le32_to_cpu(sblk->inodes),
				sblk->inodes, sblk->block_size);

	dir = (struct squashfs_dir_inode *)table;
	ldir = (struct squashfs_ldir_inode *)table;

	/* get directory offset in directory table */
	offset = sqfs_dir_offset(table, m_list, m_count);
	dirs->table = &dirs->dir_table[offset];

	/* Setup directory header */
	dirs->dir_header = malloc(SQFS_DIR_HEADER_SIZE);
	if (!dirs->dir_header)
		return -ENOMEM;

	memcpy(dirs->dir_header, dirs->table, SQFS_DIR_HEADER_SIZE);

	/* Initialize squashfs_dir_stream members */
	dirs->table += SQFS_DIR_HEADER_SIZE;
	dirs->size = get_unaligned_le16(&dir->file_size) - SQFS_DIR_HEADER_SIZE;
	dirs->entry_count = dirs->dir_header->count + 1;

	/* No path given -> root directory */
	if (!strcmp(token_list[0], "/")) {
		dirs->table = &dirs->dir_table[offset];
		memcpy(&dirs->i_dir, dir, sizeof(*dir));
		return 0;
	}

	for (j = 0; j < token_count; j++) {
		if (!sqfs_is_dir(get_unaligned_le16(&dir->inode_type))) {
			printf("** Cannot find directory. **\n");
			ret = -EINVAL;
			goto out;
		}

		while (!sqfs_readdir(dirsp, &dent)) {
			ret = strcmp(dent->name, token_list[j]);
			if (!ret)
				break;
			free(dirs->entry);
			dirs->entry = NULL;
		}

		if (ret) {
			printf("** Cannot find directory. **\n");
			ret = -EINVAL;
			goto out;
		}

		/* Redefine inode as the found token */
		new_inode_number = dirs->entry->inode_offset +
			dirs->dir_header->inode_number;

		/* Get reference to inode in the inode table */
		table = sqfs_find_inode(dirs->inode_table, new_inode_number,
					sblk->inodes, sblk->block_size);
		dir = (struct squashfs_dir_inode *)table;

		/* Check for symbolic link and inode type sanity */
		if (get_unaligned_le16(&dir->inode_type) == SQFS_SYMLINK_TYPE) {
			sym = (struct squashfs_symlink_inode *)table;
			/* Get first j + 1 tokens */
			path = sqfs_concat_tokens(token_list, j + 1);
			if (!path) {
				ret = -ENOMEM;
				goto out;
			}
			/* Resolve for these tokens */
			target = sqfs_resolve_symlink(sym, path);
			if (!target) {
				ret = -ENOMEM;
				goto out;
			}
			/* Join remaining tokens */
			rem = sqfs_concat_tokens(token_list + j + 1, token_count -
						 j - 1);
			if (!rem) {
				ret = -ENOMEM;
				goto out;
			}
			/* Concatenate remaining tokens and symlink's target */
			res = malloc(strlen(rem) + strlen(target) + 1);
			if (!res) {
				ret = -ENOMEM;
				goto out;
			}
			strcpy(res, target);
			res[strlen(target)] = '/';
			strcpy(res + strlen(target) + 1, rem);
			token_count = sqfs_count_tokens(res);

			if (token_count < 0) {
				ret = -EINVAL;
				goto out;
			}

			sym_tokens = malloc(token_count * sizeof(char *));
			if (!sym_tokens) {
				ret = -EINVAL;
				goto out;
			}

			/* Fill tokens list */
			ret = sqfs_tokenize(sym_tokens, token_count, res);
			if (ret) {
				ret = -EINVAL;
				goto out;
			}
			free(dirs->entry);
			dirs->entry = NULL;

			ret = sqfs_search_dir(dirs, sym_tokens, token_count,
					      m_list, m_count);
			goto out;
		} else if (!sqfs_is_dir(get_unaligned_le16(&dir->inode_type))) {
			printf("** Cannot find directory. **\n");
			free(dirs->entry);
			dirs->entry = NULL;
			ret = -EINVAL;
			goto out;
		}

		/* Check if it is an extended dir. */
		if (get_unaligned_le16(&dir->inode_type) == SQFS_LDIR_TYPE)
			ldir = (struct squashfs_ldir_inode *)table;

		/* Get dir. offset into the directory table */
		offset = sqfs_dir_offset(table, m_list, m_count);
		dirs->table = &dirs->dir_table[offset];

		/* Copy directory header */
		memcpy(dirs->dir_header, &dirs->dir_table[offset],
		       SQFS_DIR_HEADER_SIZE);

		/* Check for empty directory */
		if (sqfs_is_empty_dir(table)) {
			printf("Empty directory.\n");
			free(dirs->entry);
			dirs->entry = NULL;
			ret = SQFS_EMPTY_DIR;
			goto out;
		}

		dirs->table += SQFS_DIR_HEADER_SIZE;
		dirs->size = get_unaligned_le16(&dir->file_size);
		dirs->entry_count = dirs->dir_header->count + 1;
		dirs->size -= SQFS_DIR_HEADER_SIZE;
		free(dirs->entry);
		dirs->entry = NULL;
	}

	offset = sqfs_dir_offset(table, m_list, m_count);
	dirs->table = &dirs->dir_table[offset];

	if (get_unaligned_le16(&dir->inode_type) == SQFS_DIR_TYPE)
		memcpy(&dirs->i_dir, dir, sizeof(*dir));
	else
		memcpy(&dirs->i_ldir, ldir, sizeof(*ldir));

out:
	free(res);
	free(rem);
	free(path);
	free(target);
	free(sym_tokens);
	return ret;
}


int sqfs_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition)
{
	struct squashfs_super_block *sblk;
	int ret;

	ctxt.cur_dev = fs_dev_desc;
	ctxt.cur_part_info = *fs_partition;

	ret = sqfs_read_sblk(&sblk);
	if (ret)
		goto error;

	/* Make sure it has a valid SquashFS magic number*/
	if (get_unaligned_le32(&sblk->s_magic) != SQFS_MAGIC_NUMBER) {
		debug("Bad magic number for SquashFS image.\n");
		ret = -EINVAL;
		goto error;
	}

	ctxt.sblk = sblk;

	ret = sqfs_decompressor_init(&ctxt);
	if (ret) {
		goto error;
	}

	return 0;
error:
	ctxt.cur_dev = NULL;
	free(ctxt.sblk);
	ctxt.sblk = NULL;
	return ret;
}


int sqfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp)
{
	struct squashfs_super_block *sblk = ctxt.sblk;
	struct squashfs_dir_stream *dirs;
	struct squashfs_lreg_inode *lreg;
	struct squashfs_base_inode *base;
	struct squashfs_reg_inode *reg;
	int i_number, offset = 0, ret;
	struct fs_dirent *dent;
	unsigned char *ipos;
	u16 name_size;

	dirs = (struct squashfs_dir_stream *)fs_dirs;
	if (!dirs->size) {
		*dentp = NULL;
		return -SQFS_STOP_READDIR;
	}

	dent = &dirs->dentp;

	if (!dirs->entry_count) {
		if (dirs->size > SQFS_DIR_HEADER_SIZE) {
			dirs->size -= SQFS_DIR_HEADER_SIZE;
		} else {
			*dentp = NULL;
			dirs->size = 0;
			return -SQFS_STOP_READDIR;
		}

		if (dirs->size > SQFS_EMPTY_FILE_SIZE) {
			/* Read follow-up (emitted) dir. header */
			memcpy(dirs->dir_header, dirs->table,
			       SQFS_DIR_HEADER_SIZE);
			dirs->entry_count = dirs->dir_header->count + 1;
			ret = sqfs_read_entry(&dirs->entry, dirs->table +
					      SQFS_DIR_HEADER_SIZE);
			if (ret)
				return -SQFS_STOP_READDIR;

			dirs->table += SQFS_DIR_HEADER_SIZE;
		}
	} else {
		ret = sqfs_read_entry(&dirs->entry, dirs->table);
		if (ret)
			return -SQFS_STOP_READDIR;
	}

	i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
	ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
			       sblk->block_size);

	base = (struct squashfs_base_inode *)ipos;

	/* Set entry type and size */
	switch (dirs->entry->type) {
	case SQFS_DIR_TYPE:
	case SQFS_LDIR_TYPE:
		dent->type = FS_DT_DIR;
		break;
	case SQFS_REG_TYPE:
	case SQFS_LREG_TYPE:
		/*
		 * Entries do not differentiate extended from regular types, so
		 * it needs to be verified manually.
		 */
		if (get_unaligned_le16(&base->inode_type) == SQFS_LREG_TYPE) {
			lreg = (struct squashfs_lreg_inode *)ipos;
			dent->size = get_unaligned_le64(&lreg->file_size);
		} else {
			reg = (struct squashfs_reg_inode *)ipos;
			dent->size = get_unaligned_le32(&reg->file_size);
		}

		dent->type = FS_DT_REG;
		break;
	case SQFS_BLKDEV_TYPE:
	case SQFS_CHRDEV_TYPE:
	case SQFS_LBLKDEV_TYPE:
	case SQFS_LCHRDEV_TYPE:
	case SQFS_FIFO_TYPE:
	case SQFS_SOCKET_TYPE:
	case SQFS_LFIFO_TYPE:
	case SQFS_LSOCKET_TYPE:
		dent->type = SQFS_MISC_ENTRY_TYPE;
		break;
	case SQFS_SYMLINK_TYPE:
	case SQFS_LSYMLINK_TYPE:
		dent->type = FS_DT_LNK;
		break;
	default:
		return -SQFS_STOP_READDIR;
	}

	/* Set entry name (capped at FS_DIRENT_NAME_LEN which is a U-Boot limitation) */
	name_size = min_t(u16, dirs->entry->name_size + 1, FS_DIRENT_NAME_LEN - 1);
	strncpy(dent->name, dirs->entry->name, name_size);
	dent->name[name_size] = '\0';

	offset = dirs->entry->name_size + 1 + SQFS_ENTRY_BASE_LENGTH;
	dirs->entry_count--;

	/* Decrement size to be read */
	if (dirs->size > offset)
		dirs->size -= offset;
	else
		dirs->size = 0;

	/* Keep a reference to the current entry before incrementing it */
	dirs->table += offset;

	*dentp = dent;

	return 0;
}


static char *sqfs_get_abs_path(const char *base, const char *rel)
{
	char **base_tokens, **rel_tokens, *resolved = NULL;
	int ret, bc, rc, i, updir = 0, resolved_size = 0, offset = 0;

	base_tokens = NULL;
	rel_tokens = NULL;

	/* Memory allocation for the token lists */
	bc = sqfs_count_tokens(base);
	rc = sqfs_count_tokens(rel);
	if (bc < 1 || rc < 1)
		return NULL;

	base_tokens = calloc(bc, sizeof(char *));
	if (!base_tokens)
		return NULL;

	rel_tokens = calloc(rc, sizeof(char *));
	if (!rel_tokens)
		goto out;

	/* Fill token lists */
	ret = sqfs_tokenize(base_tokens, bc, base);
	if (ret)
		goto out;

	ret = sqfs_tokenize(rel_tokens, rc, rel);
	if (ret)
		goto out;

	/* count '..' occurrences in target path */
	for (i = 0; i < rc; i++) {
		if (!strcmp(rel_tokens[i], ".."))
			updir++;
	}

	/* Remove the last token and the '..' occurrences */
	bc = sqfs_clean_base_path(base_tokens, bc, updir);
	if (bc < 0)
		goto out;

	/* Calculate resolved path size */
	if (!bc)
		resolved_size++;

	resolved_size += sqfs_get_tokens_length(base_tokens, bc) +
		sqfs_get_tokens_length(rel_tokens, rc);

	resolved = malloc(resolved_size + 1);
	if (!resolved)
		goto out;

	/* Set resolved path */
	memset(resolved, '\0', resolved_size + 1);
	offset += sqfs_join(base_tokens, resolved + offset, 0, bc, '/');
	resolved[offset++] = '/';
	offset += sqfs_join(rel_tokens, resolved + offset, updir, rc, '/');

out:
	if (rel_tokens)
		for (i = 0; i < rc; i++)
			free(rel_tokens[i]);
	if (base_tokens)
		for (i = 0; i < bc; i++)
			free(base_tokens[i]);

	free(rel_tokens);
	free(base_tokens);

	return resolved;
}


void sqfs_closedir(struct fs_dir_stream *dirs)
{
	struct squashfs_dir_stream *sqfs_dirs;

	if (!dirs)
		return;

	sqfs_dirs = (struct squashfs_dir_stream *)dirs;
	free(sqfs_dirs->inode_table);
	free(sqfs_dirs->dir_table);
	free(sqfs_dirs->dir_header);
	free(sqfs_dirs);
}


static int sqfs_tokenize(char **tokens, int count, const char *str)
{
	int i, j, ret = 0;
	char *aux, *strc;

	strc = strdup(str);
	if (!strc)
		return -ENOMEM;

	if (!strcmp(strc, "/")) {
		tokens[0] = strdup(strc);
		if (!tokens[0]) {
			ret = -ENOMEM;
			goto free_strc;
		}
	} else {
		for (j = 0; j < count; j++) {
			aux = strtok(!j ? strc : NULL, "/");
			tokens[j] = strdup(aux);
			if (!tokens[j]) {
				for (i = 0; i < j; i++)
					free(tokens[i]);
				ret = -ENOMEM;
				goto free_strc;
			}
		}
	}

free_strc:
	free(strc);

	return ret;
}


static int sqfs_read_sblk(struct squashfs_super_block **sblk)
{
	*sblk = malloc_cache_aligned(ctxt.cur_dev->blksz);
	if (!*sblk)
		return -ENOMEM;

	if (sqfs_disk_read(0, 1, *sblk) != 1) {
		free(*sblk);
		sblk = NULL;
		return -EINVAL;
	}

	return 0;
}


static int sqfs_get_lregfile_info(struct squashfs_lreg_inode *lreg,
				  struct squashfs_file_info *finfo,
				  struct squashfs_fragment_block_entry *fentry,
				 __le32 blksz)
{
	int datablk_count = 0, ret;

	finfo->size = get_unaligned_le64(&lreg->file_size);
	finfo->offset = get_unaligned_le32(&lreg->offset);
	finfo->start = get_unaligned_le64(&lreg->start_block);
	finfo->frag = SQFS_IS_FRAGMENTED(get_unaligned_le32(&lreg->fragment));

	if (finfo->frag && finfo->offset == 0xFFFFFFFF)
		return -EINVAL;

	if (finfo->size < 1 || finfo->start == 0x7FFFFFFF)
		return -EINVAL;

	if (finfo->frag) {
		datablk_count = finfo->size / le32_to_cpu(blksz);
		ret = sqfs_frag_lookup(get_unaligned_le32(&lreg->fragment),
				       fentry);
		if (ret < 0)
			return -EINVAL;
		finfo->comp = ret;
		if (fentry->size < 1 || fentry->start == 0x7FFFFFFF)
			return -EINVAL;
	} else {
		datablk_count = DIV_ROUND_UP(finfo->size, le32_to_cpu(blksz));
	}

	finfo->blk_sizes = malloc(datablk_count * sizeof(u32));
	if (!finfo->blk_sizes)
		return -ENOMEM;

	return datablk_count;
}


static int sqfs_join(char **strings, char *dest, int start, int end,
		     char separator)
{
	int i, offset = 0;

	for (i = start; i < end; i++) {
		strcpy(dest + offset, strings[i]);
		offset += strlen(strings[i]);
		if (i < end - 1)
			dest[offset++] = separator;
	}

	return offset;
}


static int sqfs_get_metablk_pos(u32 *pos_list, void *table, u32 offset,
				int metablks_count)
{
	u32 data_size, cur_size = 0;
	int j, ret = 0;
	bool comp;

	if (!metablks_count)
		return -EINVAL;

	for (j = 0; j < metablks_count; j++) {
		ret = sqfs_read_metablock(table, offset + cur_size, &comp,
					  &data_size);
		if (ret)
			return -EINVAL;

		cur_size += data_size + SQFS_HEADER_SIZE;
		pos_list[j] = cur_size;
	}

	return ret;
}


static int sqfs_count_tokens(const char *filename)
{
	int token_count = 1, l;

	for (l = 1; l < strlen(filename); l++) {
		if (filename[l] == '/')
			token_count++;
	}

	/* Ignore trailing '/' in path */
	if (filename[strlen(filename) - 1] == '/')
		token_count--;

	if (!token_count)
		token_count = 1;

	return token_count;
}


static int sqfs_clean_base_path(char **base, int count, int updir)
{
	int i;

	for (i = count - updir - 1; i < count; i++)
		free(base[i]);

	return count - updir - 1;
}


int mempool_create_with_shared_mem(
    size_t pool_item_size, size_t pool_initial_size, size_t pool_expansion_size,
    func_mem_available_callback_type mem_get_free_space_func,
    func_mark_mem_used_callback_type mem_mark_used_space_func,
    func_mark_mem_free_callback_type mem_mark_free_space_func,
    func_log_callback_type log_callback_func, int flags,
    MemoryPoolHandle *p_handle) {
  int rc = 0;
  struct mempool *pool = NULL;
  if (mem_get_free_space_func == NULL || mem_mark_used_space_func == NULL ||
      mem_mark_free_space_func == NULL || p_handle == NULL) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  if (pool_initial_size > mem_get_free_space_func()) {
    return S3_MEMPOOL_THRESHOLD_EXCEEDED;
  }

  rc = mempool_create(pool_item_size, pool_initial_size, pool_expansion_size, 0,
                      log_callback_func, flags, p_handle);
  if (rc != 0) {
    return rc;
  }

  pool = (struct mempool *)*p_handle;

  pool->mem_get_free_space_func = mem_get_free_space_func;
  pool->mem_mark_used_space_func = mem_mark_used_space_func;
  pool->mem_mark_free_space_func = mem_mark_free_space_func;

  /* Explicitly mark used space, since mempool_create -> freelist_allocate
     dont have the function callbacks set. */
  pool->mem_mark_used_space_func(pool->total_bufs_allocated_by_pool *
                                 pool->mempool_item_size);

  return 0;
}

avoid the unrelesed lock after the method returns (#1041)

Update s3_memory_pool.c. Avoid the unreleased lock pool->lock after the method returns.

Co-authored-by: nileshgovande <63101371+nileshgovande@users.noreply.github.com>


int mempool_reserved_space(MemoryPoolHandle handle, size_t *free_bytes) {
  struct mempool *pool = (struct mempool *)handle;

  if ((pool == NULL) || (free_bytes == NULL)) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  if (pool->flags & ENABLE_LOCKING) {
    pthread_mutex_lock(&pool->lock);
  }

  *free_bytes = pool->mempool_item_size * pool->free_bufs_in_pool;

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_unlock(&pool->lock);
  }

  return 0;
}


void *mempool_getbuffer(MemoryPoolHandle handle, size_t expected_buffer_size) {
  int rc;
  int bufs_to_allocate;
  int bufs_that_can_be_allocated = 0;
  struct memory_pool_element *pool_item = NULL;
  struct mempool *pool = (struct mempool *)handle;
  char *log_msg_fmt =
      "mempool(%p): mempool_getbuffer called for invalid "
      "expected_buffer_size(%zu), current pool manages only "
      "mempool_item_size(%zu)";
  char log_msg[300];

  if (pool == NULL) {
    return NULL;
  }

  if (pool->mempool_item_size != expected_buffer_size) {
    // This should never happen unless mempool is used wrongly.
    if (pool->log_callback_func) {
      snprintf(log_msg, sizeof(log_msg), log_msg_fmt, (void *)pool,
               expected_buffer_size, pool->mempool_item_size);
      pool->log_callback_func(MEMPOOL_LOG_FATAL, log_msg);
      return NULL;
    }
  }

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_lock(&pool->lock);
  }

  /* If the free list is empty then expand the pool's free list */
  if (pool->free_bufs_in_pool == 0) {
    bufs_to_allocate = pool->expandable_size / pool->mempool_item_size;
    bufs_that_can_be_allocated = pool_can_expand_by(pool);
    if (bufs_that_can_be_allocated > 0) {
      /* We can at least allocate
         min(bufs_that_can_be_allocated, bufs_to_allocate) */
      bufs_to_allocate = ((bufs_to_allocate > bufs_that_can_be_allocated)
                              ? bufs_that_can_be_allocated
                              : bufs_to_allocate);

      rc = freelist_allocate(pool, bufs_to_allocate);
      if (rc != 0) {
        if ((pool->flags & ENABLE_LOCKING) != 0) {
          pthread_mutex_unlock(&pool->lock);
        }
        return NULL;
      }
    } else {
      /* We cannot allocate any more buffers, reached max threshold */
      if ((pool->flags & ENABLE_LOCKING) != 0) {
        pthread_mutex_unlock(&pool->lock);
      }
      return NULL;
    }
  }

  /* Done with expansion of pool in case of pre allocated pools */

  /* Logic of allocation from free list */
  /* If there is an item on the pool's free list, then take that... */
  if (pool->free_list != NULL) {
    pool_item = pool->free_list;
    pool->free_list = pool_item->next;
    pool_item->next = (struct memory_pool_element *)NULL;
    pool->free_bufs_in_pool--;
  }

  if (pool_item) {
    pool->number_of_bufs_shared++;
  }

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_unlock(&pool->lock);
  }

  return (void *)pool_item;
}


static int pool_can_expand_by(struct mempool *pool) {
  size_t available_space = 0;

  if (pool == NULL) {
    return 0;
  }

  if (pool->mem_get_free_space_func) {
    available_space = pool->mem_get_free_space_func();
  } else {
    const size_t allocated_space =
        pool->total_bufs_allocated_by_pool * pool->mempool_item_size;

    if (pool->max_memory_threshold > allocated_space) {
      available_space = pool->max_memory_threshold - allocated_space;
    }
  }

  // We can expand by at least (available_space / pool->mempool_item_size)
  // buffer count
  return available_space / pool->mempool_item_size;
}


int mempool_getbuffer_size(MemoryPoolHandle handle, size_t *buffer_size) {
  struct mempool *pool = (struct mempool *)handle;

  if ((pool == NULL) || (buffer_size == NULL)) {
    return S3_MEMPOOL_INVALID_ARG;
  }
  *buffer_size = pool->mempool_item_size;
  return 0;
}


int mempool_create(size_t pool_item_size, size_t pool_initial_size,
                   size_t pool_expansion_size, size_t pool_max_threshold_size,
                   func_log_callback_type log_callback_func, int flags,
                   MemoryPoolHandle *handle) {
  int rc;
  int bufs_to_allocate;
  struct mempool *pool = NULL;

  /* pool_max_threshold_size == 0 is possible when
     func_mem_available_callback_type is used. */
  if (pool_item_size == 0 || pool_expansion_size == 0 || handle == NULL) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  /* Minimum size of the pool's buffer will be sizeof pointer */
  if (pool_item_size < sizeof(struct memory_pool_element)) {
    pool_item_size = sizeof(struct memory_pool_element);
  }

  *handle = NULL;

  pool = (struct mempool *)calloc(1, sizeof(struct mempool));
  if (pool == NULL) {
    return S3_MEMPOOL_ERROR;
  }

  pool->flags |= flags;
  pool->mempool_item_size = pool_item_size;
  if (flags & CREATE_ALIGNED_MEMORY) {
    pool->alignment = MEMORY_ALIGNMENT;
  }

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    rc = pthread_mutex_init(&pool->lock, NULL);
    if (rc != 0) {
      free(pool);
      return S3_MEMPOOL_ERROR;
    }
  }

  *handle = (MemoryPoolHandle)pool;

  pool->log_callback_func = log_callback_func;
  pool->expandable_size = pool_expansion_size;
  pool->max_memory_threshold = pool_max_threshold_size;
  /* Figure out the size of free list to be preallocated from given initial pool
   * size */
  bufs_to_allocate = pool_initial_size / pool_item_size;

  /* Allocate the free list */
  if (bufs_to_allocate > 0) {
    rc = freelist_allocate(pool, bufs_to_allocate);
    if (rc != 0) {
      goto fail;
    }
  }
  return 0;

fail:
  mempool_destroy(handle);
  *handle = NULL;
  return S3_MEMPOOL_ERROR;
}


int mempool_downsize(MemoryPoolHandle handle, size_t mem_to_free) {
  struct mempool *pool = NULL;
  struct memory_pool_element *pool_item = NULL;
  int bufs_to_free = 0;
  int count = 0;
  char *log_msg_fmt =
      "mempool(%p): free(%p) called for pool item size(%zu) mem_to_free(%zu)";
  char log_msg[200];

  if (handle == NULL) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  pool = (struct mempool *)handle;

  /* pool is NULL or mem_to_free is not multiple of pool->mempool_item_size */
  if (pool == NULL || (mem_to_free % pool->mempool_item_size > 0)) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_lock(&pool->lock);
  }

  /* Only free what we can free */
  bufs_to_free = mem_to_free / pool->mempool_item_size;
  if (bufs_to_free > pool->free_bufs_in_pool) {
    bufs_to_free = pool->free_bufs_in_pool;
  }

  /* Free the items in free list */
  if (bufs_to_free > 0) {
    pool_item = pool->free_list;
    count = 0;
    while (count < bufs_to_free && pool_item != NULL) {
      count++;
      pool->free_list = pool_item->next;
      /* Log message about free()'ed item */
      if (pool->log_callback_func) {
        snprintf(log_msg, sizeof(log_msg), log_msg_fmt, (void *)pool,
                 (void *)pool_item, pool->mempool_item_size, mem_to_free);
        pool->log_callback_func(MEMPOOL_LOG_DEBUG, log_msg);
      }
      free(pool_item);
      pool->total_bufs_allocated_by_pool--;
      pool->free_bufs_in_pool--;
      pool_item = pool->free_list;
    }
    if (pool->mem_mark_free_space_func) {
      pool->mem_mark_free_space_func(bufs_to_free * pool->mempool_item_size);
    }
  }

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_unlock(&pool->lock);
  }
  return 0;
}


int freelist_allocate(struct mempool *pool, int items_count_to_allocate) {
  int i;
  int rc = 0;
  void *buf = NULL;
  struct memory_pool_element *pool_item = NULL;
  char *log_msg_fmt =
      "mempool(%p): %s(%zu). Allocated address(%p)  rc(%d) alignment(%zu) "
      "size(%zu)";
  char log_msg[200];

  if (pool == NULL) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  for (i = 0; i < items_count_to_allocate; i++) {
    if (pool->flags & CREATE_ALIGNED_MEMORY) {
      buf = NULL;
      rc = posix_memalign((void **)&buf, pool->alignment,
                          pool->mempool_item_size);
    } else {
      buf = malloc(pool->mempool_item_size);
    }
    if (pool->log_callback_func) {
      if (pool->flags & CREATE_ALIGNED_MEMORY) {
        snprintf(log_msg, sizeof(log_msg), log_msg_fmt, (void *)pool,
                 "posix_memalign", pool->mempool_item_size, buf, rc,
                 pool->alignment, pool->mempool_item_size);
      } else {
        snprintf(log_msg, sizeof(log_msg), log_msg_fmt, (void *)pool, "malloc",
                 pool->mempool_item_size, buf, rc, pool->alignment,
                 pool->mempool_item_size);
      }
      pool->log_callback_func(MEMPOOL_LOG_DEBUG, log_msg);
    }
    if (buf == NULL || rc != 0) {
      return S3_MEMPOOL_ERROR;
    }

    /* exclude this buffer while geneating core dump*/
    madvise(buf, pool->mempool_item_size, MADV_DONTDUMP);

    if ((pool->flags & ZEROED_BUFFER) != 0) {
      memset(buf, 0, pool->mempool_item_size);
    }

    pool->total_bufs_allocated_by_pool++;
    /* Put the allocated memory into the list */
    pool_item = (struct memory_pool_element *)buf;

    pool_item->next = pool->free_list;
    pool->free_list = pool_item;
    /* memory is pre appended to list */

    /* Increase the free list count */
    pool->free_bufs_in_pool++;
    if (pool->mem_mark_used_space_func) {
      pool->mem_mark_used_space_func(pool->mempool_item_size);
    }
  }
  return 0;
}


int mempool_releasebuffer(MemoryPoolHandle handle, void *buf,
                          size_t released_buffer_size) {
  struct mempool *pool = (struct mempool *)handle;
  struct memory_pool_element *pool_item = (struct memory_pool_element *)buf;
  char *log_msg_fmt =
      "mempool(%p): mempool_releasebuffer called for invalid "
      "released_buffer_size(%zu), current pool manages only "
      "mempool_item_size(%zu)";
  char log_msg[300];

  if ((pool == NULL) || (pool_item == NULL)) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  if (pool->mempool_item_size != released_buffer_size) {
    // This should never happen unless mempool is used wrongly.
    if (pool->log_callback_func) {
      snprintf(log_msg, sizeof(log_msg), log_msg_fmt, (void *)pool,
               released_buffer_size, pool->mempool_item_size);
      pool->log_callback_func(MEMPOOL_LOG_FATAL, log_msg);
      return S3_MEMPOOL_INVALID_ARG;
    }
  }

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_lock(&pool->lock);
  }

  /* Clean up the buffer so that we get it 'clean' when we allocate it next
   * time*/
  if ((pool->flags & ZEROED_BUFFER) != 0) {
    memset(pool_item, 0, pool->mempool_item_size);
  }

  // Add the buffer back to pool
  pool_item->next = pool->free_list;
  pool->free_list = pool_item;
  pool->free_bufs_in_pool++;
  pool_item = NULL;

  pool->number_of_bufs_shared--;

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_unlock(&pool->lock);
  }

  return 0;
}


int mempool_destroy(MemoryPoolHandle *handle) {
  struct mempool *pool = NULL;
  struct memory_pool_element *pool_item;
  char *log_msg_fmt = "mempool(%p): free(%p) called for buffer size(%zu)";
  char log_msg[200];

  if (handle == NULL) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  pool = (struct mempool *)*handle;
  if (pool == NULL) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_lock(&pool->lock);
  }

  if (*handle == NULL) {
    if ((pool->flags & ENABLE_LOCKING) != 0) {
      pthread_mutex_unlock(&pool->lock);
    }
    return S3_MEMPOOL_INVALID_ARG;
  }

  /* reset the handle */
  *handle = NULL;
  /* Free the items in free list */
  pool_item = pool->free_list;
  while (pool_item != NULL) {
    pool->free_list = pool_item->next;
    /* Log message about free()'ed item */
    if (pool->log_callback_func) {
      snprintf(log_msg, sizeof(log_msg), log_msg_fmt, (void *)pool,
               (void *)pool_item, pool->mempool_item_size);
      pool->log_callback_func(MEMPOOL_LOG_DEBUG, log_msg);
    }
    free(pool_item);
#if 0
    /* Need this if below asserts are there */
    pool->total_bufs_allocated_by_pool--;
    pool->free_bufs_in_pool--;
#endif
    pool_item = pool->free_list;
  }
  pool->free_list = NULL;

  /* TODO: libevhtp/libevent seems to hold some references and not release back
   * to pool. Bug will be logged for this to investigate.
   */
  /* Assert if there are leaks */
  /*
    assert(pool->total_bufs_allocated_by_pool == 0);
    assert(pool->number_of_bufs_shared == 0);
    assert(pool->free_bufs_in_pool == 0);
  */

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_unlock(&pool->lock);
    pthread_mutex_destroy(&pool->lock);
  }

  free(pool);
  pool = NULL;
  return 0;
}


int mempool_available_space(MemoryPoolHandle p_pool, size_t *p_avail_bytes) {
  if (!p_pool || !p_avail_bytes) {
    return S3_MEMPOOL_INVALID_ARG;
  }
  if (p_pool->flags & ENABLE_LOCKING) {
    pthread_mutex_lock(&p_pool->lock);
  }
  const size_t used_space =
      p_pool->number_of_bufs_shared * p_pool->mempool_item_size;
  const size_t max_memory_threshold = p_pool->max_memory_threshold;

  *p_avail_bytes =
      max_memory_threshold > used_space ? max_memory_threshold - used_space : 0;

  if (p_pool->flags & ENABLE_LOCKING) {
    pthread_mutex_unlock(&p_pool->lock);
  }
  return 0;
}


int mempool_getinfo(MemoryPoolHandle handle, struct pool_info *poolinfo) {
  struct mempool *pool = (struct mempool *)handle;

  if ((pool == NULL) || (poolinfo == NULL)) {
    return S3_MEMPOOL_INVALID_ARG;
  }

  if (pool->flags & ENABLE_LOCKING) {
    pthread_mutex_lock(&pool->lock);
  }

  poolinfo->mempool_item_size = pool->mempool_item_size;
  poolinfo->free_bufs_in_pool = pool->free_bufs_in_pool;
  poolinfo->number_of_bufs_shared = pool->number_of_bufs_shared;
  poolinfo->expandable_size = pool->expandable_size;
  poolinfo->total_bufs_allocated_by_pool = pool->total_bufs_allocated_by_pool;
  poolinfo->flags = pool->flags;

  if ((pool->flags & ENABLE_LOCKING) != 0) {
    pthread_mutex_unlock(&pool->lock);
  }
  return 0;
}


cleanup_process_schemap (runtime_spec_schema_config_schema_process **p)
{
  runtime_spec_schema_config_schema_process *process = *p;
  if (process)
    (void) free_runtime_spec_schema_config_schema_process (process);
}

exec: --cap do not set inheritable capabilities

Closes: CVE-2022-27650

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>


parse_opt (int key, char *arg, struct argp_state *state)
{
  switch (key)
    {
    case OPTION_CONSOLE_SOCKET:
      exec_options.console_socket = arg;
      break;

    case OPTION_PID_FILE:
      exec_options.pid_file = arg;
      break;

    case OPTION_NO_NEW_PRIVS:
      exec_options.no_new_privs = true;
      break;

    case OPTION_PROCESS_LABEL:
      exec_options.process_label = argp_mandatory_argument (arg, state);
      break;

    case OPTION_APPARMOR:
      exec_options.apparmor = argp_mandatory_argument (arg, state);
      break;

    case OPTION_PRESERVE_FDS:
      exec_options.preserve_fds = strtoul (argp_mandatory_argument (arg, state), NULL, 10);
      break;

    case OPTION_CGROUP:
      exec_options.cgroup = argp_mandatory_argument (arg, state);
      break;

    case 'd':
      exec_options.detach = true;
      break;

    case 'p':
      exec_options.process = arg;
      break;

    case 't':
      exec_options.tty = arg == NULL || (strcmp (arg, "false") != 0 && strcmp (arg, "no") != 0);
      break;

    case 'u':
      exec_options.user = arg;
      break;

    case 'e':
      append_env (arg);
      break;

    case 'c':
      append_cap (arg);
      break;

    case OPTION_CWD:
      exec_options.cwd = xstrdup (arg);
      break;

    case ARGP_KEY_NO_ARGS:
      libcrun_fail_with_error (0, "please specify a ID for the container");

    default:
      return ARGP_ERR_UNKNOWN;
    }

  return 0;
}


append_env (const char *arg)
{
  exec_options.env = realloc (exec_options.env, (exec_options.env_size + 2) * sizeof (*exec_options.env));
  if (exec_options.env == NULL)
    error (EXIT_FAILURE, errno, "cannot allocate memory");
  exec_options.env[exec_options.env_size + 1] = NULL;
  exec_options.env[exec_options.env_size] = xstrdup (arg);
  exec_options.env_size++;
}


make_oci_process_user (const char *userspec)
{
  runtime_spec_schema_config_schema_process_user *u;
  char *endptr = NULL;

  if (userspec == NULL)
    return NULL;

  u = xmalloc0 (sizeof (runtime_spec_schema_config_schema_process_user));
  errno = 0;
  u->uid = strtol (userspec, &endptr, 10);
  if (errno == ERANGE)
    libcrun_fail_with_error (0, "invalid UID specified");
  if (*endptr == '\0')
    return u;
  if (*endptr != ':')
    libcrun_fail_with_error (0, "invalid USERSPEC specified");

  errno = 0;
  u->gid = strtol (endptr + 1, &endptr, 10);
  if (errno == ERANGE)
    libcrun_fail_with_error (0, "invalid GID specified");
  if (*endptr != '\0')
    libcrun_fail_with_error (0, "invalid USERSPEC specified");

  return u;
}


crun_command_exec (struct crun_global_arguments *global_args, int argc, char **argv, libcrun_error_t *err)
{
  int first_arg = 0, ret = 0;
  libcrun_context_t crun_context = {
    0,
  };
  cleanup_process_schema runtime_spec_schema_config_schema_process *process = NULL;
  struct libcrun_container_exec_options_s exec_opts;

  memset (&exec_opts, 0, sizeof (exec_opts));
  exec_opts.struct_size = sizeof (exec_opts);

  crun_context.preserve_fds = 0;
  crun_context.listen_fds = 0;

  argp_parse (&run_argp, argc, argv, ARGP_IN_ORDER, &first_arg, &exec_options);
  crun_assert_n_args (argc - first_arg, exec_options.process ? 1 : 2, -1);

  ret = init_libcrun_context (&crun_context, argv[first_arg], global_args, err);
  if (UNLIKELY (ret < 0))
    return ret;

  crun_context.detach = exec_options.detach;
  crun_context.console_socket = exec_options.console_socket;
  crun_context.pid_file = exec_options.pid_file;
  crun_context.preserve_fds = exec_options.preserve_fds;

  if (getenv ("LISTEN_FDS"))
    {
      crun_context.listen_fds = strtoll (getenv ("LISTEN_FDS"), NULL, 10);
      crun_context.preserve_fds += crun_context.listen_fds;
    }

  if (exec_options.process)
    exec_opts.path = exec_options.process;
  else
    {
      process = xmalloc0 (sizeof (*process));
      int i;

      process->args_len = argc;
      process->args = xmalloc0 ((argc + 1) * sizeof (*process->args));
      for (i = 0; i < argc - first_arg; i++)
        process->args[i] = xstrdup (argv[first_arg + i + 1]);
      process->args[i] = NULL;
      if (exec_options.cwd)
        process->cwd = exec_options.cwd;
      process->terminal = exec_options.tty;
      process->env = exec_options.env;
      process->env_len = exec_options.env_size;
      process->user = make_oci_process_user (exec_options.user);

      if (exec_options.process_label != NULL)
        process->selinux_label = exec_options.process_label;

      if (exec_options.apparmor != NULL)
        process->apparmor_profile = exec_options.apparmor;

      if (exec_options.cap_size > 0)
        {
          runtime_spec_schema_config_schema_process_capabilities *capabilities
              = xmalloc (sizeof (runtime_spec_schema_config_schema_process_capabilities));

          capabilities->effective = exec_options.cap;
          capabilities->effective_len = exec_options.cap_size;

          capabilities->inheritable = NULL;
          capabilities->inheritable_len = 0;

          capabilities->bounding = dup_array (exec_options.cap, exec_options.cap_size);
          capabilities->bounding_len = exec_options.cap_size;

          capabilities->ambient = dup_array (exec_options.cap, exec_options.cap_size);
          capabilities->ambient_len = exec_options.cap_size;

          capabilities->permitted = dup_array (exec_options.cap, exec_options.cap_size);
          capabilities->permitted_len = exec_options.cap_size;

          process->capabilities = capabilities;
        }

      // noNewPriviledges will remain `false` if basespec has `false` unless specified
      // Default is always `true` in generated basespec config
      if (exec_options.no_new_privs)
        process->no_new_privileges = 1;

      exec_opts.process = process;
    }

  exec_opts.cgroup = exec_options.cgroup;

  return libcrun_container_exec_with_options (&crun_context, argv[first_arg], &exec_opts, err);
}


append_cap (const char *arg)
{
  exec_options.cap = realloc (exec_options.cap, (exec_options.cap_size + 2) * sizeof (*exec_options.cap));
  if (exec_options.cap == NULL)
    error (EXIT_FAILURE, errno, "cannot allocate memory");
  exec_options.cap[exec_options.cap_size + 1] = NULL;
  exec_options.cap[exec_options.cap_size] = xstrdup (arg);
  exec_options.cap_size++;
}


dup_array (char **arr, size_t len)
{
  size_t i;
  char **ret;

  ret = malloc (sizeof (char *) * (len + 1));
  if (ret == NULL)
    error (EXIT_FAILURE, errno, "cannot allocate memory");
  for (i = 0; i < len; i++)
    ret[i] = xstrdup (arr[i]);

  ret[i] = NULL;
  return ret;
}


vm_execute (vm_frame_ctx_t *frame_ctx_p) /**< frame context */
{
  while (true)
  {
    ecma_value_t completion_value = vm_loop (frame_ctx_p);

    switch (frame_ctx_p->call_operation)
    {
      case VM_EXEC_CALL:
      {
        opfunc_call (frame_ctx_p);
        break;
      }
#if JERRY_ESNEXT
      case VM_EXEC_SUPER_CALL:
      {
        vm_super_call (frame_ctx_p);
        break;
      }
      case VM_EXEC_SPREAD_OP:
      {
        vm_spread_operation (frame_ctx_p);
        break;
      }
      case VM_EXEC_RETURN:
      {
        return completion_value;
      }
#endif /* JERRY_ESNEXT */
      case VM_EXEC_CONSTRUCT:
      {
        opfunc_construct (frame_ctx_p);
        break;
      }
      default:
      {
        JERRY_ASSERT (frame_ctx_p->call_operation == VM_NO_EXEC_OP);

        const ecma_compiled_code_t *bytecode_header_p = frame_ctx_p->shared_p->bytecode_header_p;
        uint32_t register_end;

        if (bytecode_header_p->status_flags & CBC_CODE_FLAGS_UINT16_ARGUMENTS)
        {
          register_end = ((cbc_uint16_arguments_t *) bytecode_header_p)->register_end;
        }
        else
        {
          register_end = ((cbc_uint8_arguments_t *) bytecode_header_p)->register_end;
        }

        /* Free arguments and registers */
        ecma_value_t *registers_p = VM_GET_REGISTERS (frame_ctx_p);
        for (uint32_t i = 0; i < register_end; i++)
        {
          ecma_fast_free_value (registers_p[i]);
        }

#if JERRY_DEBUGGER
        if (JERRY_CONTEXT (debugger_stop_context) == JERRY_CONTEXT (vm_top_context_p))
        {
          /* The engine will stop when the next breakpoint is reached. */
          JERRY_ASSERT (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_VM_STOP);
          JERRY_CONTEXT (debugger_stop_context) = NULL;
        }
#endif /* JERRY_DEBUGGER */

        JERRY_CONTEXT (vm_top_context_p) = frame_ctx_p->prev_context_p;
        return completion_value;
      }
    }
  }
} /* vm_execute */

Fix for-in collection cleanup on abrupt 'has' result (#4807)

This patch fixes #4747

JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik robert.fancsik@h-lab.eu


vm_op_get_value (ecma_value_t object, /**< base object */
                 ecma_value_t property) /**< property name */
{
  if (ecma_is_value_object (object))
  {
    ecma_object_t *object_p = ecma_get_object_from_value (object);
    ecma_string_t *property_name_p = NULL;

    if (ecma_is_value_integer_number (property))
    {
      ecma_integer_value_t int_value = ecma_get_integer_from_value (property);

      if (int_value >= 0 && int_value <= ECMA_DIRECT_STRING_MAX_IMM)
      {
        if (ecma_get_object_type (object_p) == ECMA_OBJECT_TYPE_ARRAY)
        {
          ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p;

          if (JERRY_LIKELY (ecma_op_array_is_fast_array (ext_object_p)
                            && (uint32_t) int_value < ext_object_p->u.array.length))
          {
            ecma_value_t *values_p = ECMA_GET_NON_NULL_POINTER (ecma_value_t, object_p->u1.property_list_cp);

            if (JERRY_LIKELY (!ecma_is_value_array_hole (values_p[int_value])))
            {
              return ecma_fast_copy_value (values_p[int_value]);
            }
          }
        }

        property_name_p = (ecma_string_t *) ECMA_CREATE_DIRECT_STRING (ECMA_DIRECT_STRING_UINT,
                                                                       (uintptr_t) int_value);
      }
    }
    else if (ecma_is_value_string (property))
    {
      property_name_p = ecma_get_string_from_value (property);
    }

#if JERRY_ESNEXT
    if (ecma_is_value_symbol (property))
    {
      property_name_p = ecma_get_symbol_from_value (property);
    }
#endif /* JERRY_ESNEXT */

    if (property_name_p != NULL)
    {
#if JERRY_LCACHE
      ecma_property_t *property_p = ecma_lcache_lookup (object_p, property_name_p);

      if (property_p != NULL && (*property_p & ECMA_PROPERTY_FLAG_DATA))
      {
        JERRY_ASSERT (!ECMA_PROPERTY_IS_INTERNAL (*property_p));
        return ecma_fast_copy_value (ECMA_PROPERTY_VALUE_PTR (property_p)->value);
      }
#endif /* JERRY_LCACHE */

      /* There is no need to free the name. */
      return ecma_op_object_get (object_p, property_name_p);
    }
  }

  if (JERRY_UNLIKELY (ecma_is_value_undefined (object) || ecma_is_value_null (object)))
  {
#if JERRY_ERROR_MESSAGES
    ecma_value_t error_value = ecma_raise_standard_error_with_format (JERRY_ERROR_TYPE,
                                                                      "Cannot read property '%' of %",
                                                                      property,
                                                                      object);
#else /* !JERRY_ERROR_MESSAGES */
    ecma_value_t error_value = ecma_raise_type_error (NULL);
#endif /* JERRY_ERROR_MESSAGES */
    return error_value;
  }

  ecma_string_t *property_name_p = ecma_op_to_property_key (property);

  if (property_name_p == NULL)
  {
    return ECMA_VALUE_ERROR;
  }

  ecma_value_t get_value_result = ecma_op_get_value_object_base (object, property_name_p);

  ecma_deref_ecma_string (property_name_p);
  return get_value_result;
} /* vm_op_get_value */


vm_run_eval (ecma_compiled_code_t *bytecode_data_p, /**< byte-code data */
             uint32_t parse_opts) /**< ecma_parse_opts_t option bits */
{
  ecma_value_t this_binding;
  ecma_object_t *lex_env_p;

  /* ECMA-262 v5, 10.4.2 */
  if (parse_opts & ECMA_PARSE_DIRECT_EVAL)
  {
    this_binding = ecma_copy_value (JERRY_CONTEXT (vm_top_context_p)->this_binding);
    lex_env_p = JERRY_CONTEXT (vm_top_context_p)->lex_env_p;

#if JERRY_DEBUGGER
    uint32_t chain_index = parse_opts >> ECMA_PARSE_CHAIN_INDEX_SHIFT;
    parse_opts &= (1 << ECMA_PARSE_CHAIN_INDEX_SHIFT) - 1;

    while (chain_index != 0)
    {
      if (JERRY_UNLIKELY (lex_env_p->u2.outer_reference_cp == JMEM_CP_NULL))
      {
        ecma_bytecode_deref (bytecode_data_p);
        ecma_free_value (this_binding);
        return ecma_raise_range_error (ECMA_ERR_MSG ("Invalid scope chain index for eval"));
      }

      lex_env_p = ECMA_GET_NON_NULL_POINTER (ecma_object_t, lex_env_p->u2.outer_reference_cp);

      if ((ecma_get_lex_env_type (lex_env_p) == ECMA_LEXICAL_ENVIRONMENT_THIS_OBJECT_BOUND)
          || (ecma_get_lex_env_type (lex_env_p) == ECMA_LEXICAL_ENVIRONMENT_DECLARATIVE))
      {
        chain_index--;
      }
    }
#endif /* JERRY_DEBUGGER */
  }
  else
  {
#if JERRY_BUILTIN_REALMS
    ecma_object_t *global_obj_p = (ecma_object_t *) ecma_op_function_get_realm (bytecode_data_p);
    this_binding = ((ecma_global_object_t *) global_obj_p)->this_binding;
    ecma_ref_object (ecma_get_object_from_value (this_binding));
#else /* !JERRY_BUILTIN_REALMS */
    ecma_object_t *global_obj_p = ecma_builtin_get_global ();
    ecma_ref_object (global_obj_p);
    this_binding = ecma_make_object_value (global_obj_p);
#endif /* JERRY_BUILTIN_REALMS */
    lex_env_p = ecma_get_global_scope (global_obj_p);
  }

  ecma_ref_object (lex_env_p);

  if ((bytecode_data_p->status_flags & CBC_CODE_FLAGS_STRICT_MODE) != 0)
  {
    ecma_object_t *strict_lex_env_p = ecma_create_decl_lex_env (lex_env_p);

    ecma_deref_object (lex_env_p);
    lex_env_p = strict_lex_env_p;
  }

  if ((bytecode_data_p->status_flags & CBC_CODE_FLAGS_LEXICAL_BLOCK_NEEDED) != 0)
  {
    ecma_object_t *lex_block_p = ecma_create_decl_lex_env (lex_env_p);
    lex_block_p->type_flags_refs |= ECMA_OBJECT_FLAG_BLOCK;

    ecma_deref_object (lex_env_p);
    lex_env_p = lex_block_p;
  }

  vm_frame_ctx_shared_t shared;
  shared.bytecode_header_p = bytecode_data_p;
  shared.function_object_p = NULL;
  shared.status_flags = (parse_opts & ECMA_PARSE_DIRECT_EVAL) ? VM_FRAME_CTX_SHARED_DIRECT_EVAL : 0;

  ecma_value_t completion_value = vm_run (&shared, this_binding, lex_env_p);

  ecma_deref_object (lex_env_p);
  ecma_free_value (this_binding);

#if JERRY_SNAPSHOT_EXEC
  if (!(bytecode_data_p->status_flags & CBC_CODE_FLAGS_STATIC_FUNCTION))
  {
    ecma_bytecode_deref (bytecode_data_p);
  }
#else /* !JERRY_SNAPSHOT_EXEC */
  ecma_bytecode_deref (bytecode_data_p);
#endif /* JERRY_SNAPSHOT_EXEC */

  return completion_value;
} /* vm_run_eval */


vm_init_exec (vm_frame_ctx_t *frame_ctx_p) /**< frame context */
{
  vm_frame_ctx_shared_t *shared_p = frame_ctx_p->shared_p;
  const ecma_compiled_code_t *bytecode_header_p = shared_p->bytecode_header_p;

  frame_ctx_p->prev_context_p = JERRY_CONTEXT (vm_top_context_p);
  frame_ctx_p->context_depth = 0;
  frame_ctx_p->status_flags = (uint8_t) ((shared_p->status_flags & VM_FRAME_CTX_DIRECT_EVAL)
                                         | (bytecode_header_p->status_flags & VM_FRAME_CTX_IS_STRICT));

  uint16_t argument_end, register_end;
  ecma_value_t *literal_p;

  if (bytecode_header_p->status_flags & CBC_CODE_FLAGS_UINT16_ARGUMENTS)
  {
    cbc_uint16_arguments_t *args_p = (cbc_uint16_arguments_t *) bytecode_header_p;

    argument_end = args_p->argument_end;
    register_end = args_p->register_end;

    literal_p = (ecma_value_t *) (args_p + 1);
    literal_p -= register_end;
    frame_ctx_p->literal_start_p = literal_p;
    literal_p += args_p->literal_end;
  }
  else
  {
    cbc_uint8_arguments_t *args_p = (cbc_uint8_arguments_t *) bytecode_header_p;

    argument_end = args_p->argument_end;
    register_end = args_p->register_end;

    literal_p = (ecma_value_t *) (args_p + 1);
    literal_p -= register_end;
    frame_ctx_p->literal_start_p = literal_p;
    literal_p += args_p->literal_end;
  }

  frame_ctx_p->byte_code_p = (uint8_t *) literal_p;
  frame_ctx_p->byte_code_start_p = (uint8_t *) literal_p;
  frame_ctx_p->stack_top_p = VM_GET_REGISTERS (frame_ctx_p) + register_end;

  uint32_t arg_list_len = 0;

  if (argument_end > 0)
  {
    JERRY_ASSERT (shared_p->status_flags & VM_FRAME_CTX_SHARED_HAS_ARG_LIST);

    const ecma_value_t *arg_list_p = ((vm_frame_ctx_shared_args_t *) shared_p)->arg_list_p;
    arg_list_len = ((vm_frame_ctx_shared_args_t *) shared_p)->arg_list_len;

    if (arg_list_len > argument_end)
    {
      arg_list_len = argument_end;
    }

    for (uint32_t i = 0; i < arg_list_len; i++)
    {
      VM_GET_REGISTER (frame_ctx_p, i) = ecma_fast_copy_value (arg_list_p[i]);
    }
  }

  /* The arg_list_len contains the end of the copied arguments.
   * Fill everything else with undefined. */
  if (register_end > arg_list_len)
  {
    ecma_value_t *stack_p = VM_GET_REGISTERS (frame_ctx_p) + arg_list_len;

    for (uint32_t i = arg_list_len; i < register_end; i++)
    {
      *stack_p++ = ECMA_VALUE_UNDEFINED;
    }
  }

  JERRY_CONTEXT (status_flags) &= (uint32_t) ~ECMA_STATUS_DIRECT_EVAL;
  JERRY_CONTEXT (vm_top_context_p) = frame_ctx_p;
} /* vm_init_exec */


vm_run_module (ecma_module_t *module_p) /**< module to be executed */
{
  const ecma_value_t module_init_result = ecma_module_initialize (module_p);

  if (ECMA_IS_VALUE_ERROR (module_init_result))
  {
    return module_init_result;
  }

  vm_frame_ctx_shared_t shared;
  shared.bytecode_header_p = module_p->u.compiled_code_p;
  shared.function_object_p = &module_p->header.object;
  shared.status_flags = 0;

  return vm_run (&shared, ECMA_VALUE_UNDEFINED, module_p->scope_p);
} /* vm_run_module */


vm_get_class_function (vm_frame_ctx_t *frame_ctx_p) /**< frame context */
{
  JERRY_ASSERT (frame_ctx_p != NULL);

  if (frame_ctx_p->shared_p->status_flags & VM_FRAME_CTX_SHARED_NON_ARROW_FUNC)
  {
    return frame_ctx_p->shared_p->function_object_p;
  }

  ecma_environment_record_t *environment_record_p = ecma_op_get_environment_record (frame_ctx_p->lex_env_p);

  JERRY_ASSERT (environment_record_p != NULL);
  return ecma_get_object_from_value (environment_record_p->function_object);
} /* vm_get_class_function */


opfunc_construct (vm_frame_ctx_t *frame_ctx_p) /**< frame context */
{
  const uint8_t *byte_code_p = frame_ctx_p->byte_code_p + 1;
  uint8_t opcode = byte_code_p[-1];
  unsigned int arguments_list_len;

  if (opcode >= CBC_NEW0)
  {
    arguments_list_len = (unsigned int) (opcode - CBC_NEW0);
  }
  else
  {
    arguments_list_len = *byte_code_p++;
  }

  ecma_value_t *stack_top_p = frame_ctx_p->stack_top_p - arguments_list_len;
  ecma_value_t constructor_value = stack_top_p[-1];
  ecma_value_t completion_value;

  const char *constructor_message_p = ecma_check_constructor (constructor_value);
  if (constructor_message_p != ECMA_IS_VALID_CONSTRUCTOR)
  {
    completion_value = ecma_raise_type_error (constructor_message_p);
  }
  else
  {
    ecma_object_t *constructor_obj_p = ecma_get_object_from_value (constructor_value);

    completion_value = ecma_op_function_construct (constructor_obj_p,
                                                   constructor_obj_p,
                                                   stack_top_p,
                                                   arguments_list_len);
  }

  /* Free registers. */
  for (uint32_t i = 0; i < arguments_list_len; i++)
  {
    ecma_fast_free_value (stack_top_p[i]);
  }

  if (JERRY_UNLIKELY (ECMA_IS_VALUE_ERROR (completion_value)))
  {
#if JERRY_DEBUGGER
    JERRY_CONTEXT (debugger_exception_byte_code_p) = frame_ctx_p->byte_code_p;
#endif /* JERRY_DEBUGGER */
    frame_ctx_p->byte_code_p = (uint8_t *) vm_error_byte_code_p;
  }
  else
  {
    ecma_free_value (stack_top_p[-1]);
    frame_ctx_p->byte_code_p = byte_code_p;
    stack_top_p[-1] = completion_value;
  }

  frame_ctx_p->stack_top_p = stack_top_p;
} /* opfunc_construct */