int ssl_connect(struct tunnel *tunnel)
{
	ssl_disconnect(tunnel);

	tunnel->ssl_socket = tcp_connect(tunnel);
	if (tunnel->ssl_socket == -1)
		return 1;

	// registration is deprecated from OpenSSL 1.1.0 onward
#if OPENSSL_API_COMPAT < 0x10100000L
	// Register the error strings for libcrypto & libssl
	SSL_load_error_strings();
	// Register the available ciphers and digests
	SSL_library_init();
#endif

	tunnel->ssl_context = SSL_CTX_new(SSLv23_client_method());
	if (tunnel->ssl_context == NULL) {
		log_error("SSL_CTX_new: %s\n",
		          ERR_error_string(ERR_peek_last_error(), NULL));
		return 1;
	}

	// Load the OS default CA files
	if (!SSL_CTX_set_default_verify_paths(tunnel->ssl_context))
		log_error("Could not load OS OpenSSL files.\n");

	if (tunnel->config->ca_file) {
		if (!SSL_CTX_load_verify_locations(
		            tunnel->ssl_context,
		            tunnel->config->ca_file, NULL)) {
			log_error("SSL_CTX_load_verify_locations: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}
	}

	/* Use engine for PIV if user-cert config starts with pkcs11 URI: */
	if (tunnel->config->use_engine > 0) {

		ENGINE *e;
		ENGINE_load_builtin_engines();
		e = ENGINE_by_id("pkcs11");
		if (!e) {
			log_error("Could not load pkcs11 Engine: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}
		if (!ENGINE_init(e)) {
			log_error("Could not init pkcs11 Engine: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			ENGINE_free(e);
			return 1;
		}
		if (!ENGINE_set_default_RSA(e))
			abort();

		ENGINE_finish(e);
		ENGINE_free(e);

		struct token parms;
		parms.uri = tunnel->config->user_cert;
		parms.cert = NULL;

		if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &parms, NULL, 1)) {
			log_error("PKCS11 ENGINE_ctrl_cmd: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}

		if (!SSL_CTX_use_certificate(tunnel->ssl_context, parms.cert)) {
			log_error("PKCS11 SSL_CTX_use_certificate: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}

		EVP_PKEY * privkey = ENGINE_load_private_key(
		                             e, parms.uri, UI_OpenSSL(), NULL);
		if (!privkey) {
			log_error("PKCS11 ENGINE_load_private_key: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}

		if (!SSL_CTX_use_PrivateKey(tunnel->ssl_context, privkey)) {
			log_error("PKCS11 SSL_CTX_use_PrivateKey_file: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}

		if (!SSL_CTX_check_private_key(tunnel->ssl_context)) {
			log_error("PKCS11 SSL_CTX_check_private_key: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}

	} else {        /* end PKCS11-engine */

		if (tunnel->config->user_cert) {
			if (!SSL_CTX_use_certificate_file(
			            tunnel->ssl_context, tunnel->config->user_cert,
			            SSL_FILETYPE_PEM)) {
				log_error("SSL_CTX_use_certificate_file: %s\n",
				          ERR_error_string(ERR_peek_last_error(), NULL));
				return 1;
			}
		}

		if (tunnel->config->user_key) {
			if (!SSL_CTX_use_PrivateKey_file(
			            tunnel->ssl_context, tunnel->config->user_key,
			            SSL_FILETYPE_PEM)) {
				log_error("SSL_CTX_use_PrivateKey_file: %s\n",
				          ERR_error_string(ERR_peek_last_error(), NULL));
				return 1;
			}
		}

		if (tunnel->config->user_cert && tunnel->config->user_key) {
			if (!SSL_CTX_check_private_key(tunnel->ssl_context)) {
				log_error("SSL_CTX_check_private_key: %s\n",
				          ERR_error_string(ERR_peek_last_error(), NULL));
				return 1;
			}
		}
	}

	if (!tunnel->config->insecure_ssl) {
		long sslctxopt = SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION;
		long checkopt;

		checkopt = SSL_CTX_set_options(tunnel->ssl_context, sslctxopt);
		if ((checkopt & sslctxopt) != sslctxopt) {
			log_error("SSL_CTX_set_options didn't set opt: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}
	}

	tunnel->ssl_handle = SSL_new(tunnel->ssl_context);
	if (tunnel->ssl_handle == NULL) {
		log_error("SSL_new: %s\n",
		          ERR_error_string(ERR_peek_last_error(), NULL));
		return 1;
	}

	if (!tunnel->config->insecure_ssl) {
		if (!tunnel->config->cipher_list) {
			const char *cipher_list;
			if (tunnel->config->seclevel_1)
				cipher_list = "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4@SECLEVEL=1";
			else
				cipher_list = "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4";
			tunnel->config->cipher_list = strdup(cipher_list);
		}
	} else {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
		if (tunnel->config->min_tls <= 0)
			tunnel->config->min_tls = TLS1_VERSION;
#endif
		if (!tunnel->config->cipher_list && tunnel->config->seclevel_1) {
			const char *cipher_list = "DEFAULT@SECLEVEL=1";
			tunnel->config->cipher_list = strdup(cipher_list);
		}
	}

	if (tunnel->config->cipher_list) {
		log_debug("Setting cipher list to: %s\n", tunnel->config->cipher_list);
		if (!SSL_set_cipher_list(tunnel->ssl_handle,
		                         tunnel->config->cipher_list)) {
			log_error("SSL_set_cipher_list failed: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}
	}

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	if (tunnel->config->min_tls > 0) {
		log_debug("Setting min proto version to: 0x%x\n",
		          tunnel->config->min_tls);
		if (!SSL_set_min_proto_version(tunnel->ssl_handle,
		                               tunnel->config->min_tls)) {
			log_error("SSL_set_min_proto_version failed: %s\n",
			          ERR_error_string(ERR_peek_last_error(), NULL));
			return 1;
		}
	}
#endif

	if (!SSL_set_fd(tunnel->ssl_handle, tunnel->ssl_socket)) {
		log_error("SSL_set_fd: %s\n",
		          ERR_error_string(ERR_peek_last_error(), NULL));
		return 1;
	}
	SSL_set_mode(tunnel->ssl_handle, SSL_MODE_AUTO_RETRY);

	// Initiate SSL handshake
	if (SSL_connect(tunnel->ssl_handle) != 1) {
		log_error("SSL_connect: %s\n"
		          "You might want to try --insecure-ssl or specify a different --cipher-list\n",
		          ERR_error_string(ERR_peek_last_error(), NULL));
		return 1;
	}
	SSL_set_mode(tunnel->ssl_handle, SSL_MODE_AUTO_RETRY);

	if (ssl_verify_cert(tunnel))
		return 1;

	// Disable SIGPIPE (occurs when trying to write to an already-closed
	// socket).
	signal(SIGPIPE, SIG_IGN);

	return 0;
}


fix TLS Certificate CommonName NULL Byte Vulnerability

CVE-2020-7043 TLS Certificate CommonName NULL Byte Vulnerability is fixed
with this commit

with #8 hostname validation for the certificate was introduced
but unfortunately strncasecmp() was used to compare the byte array
against the expected hostname. This does not correctly treat a CN
which contains a NULL byte. In order to fix this vulnerability
the reference implementation from iSECPartners has been included
into the code.

static int pppd_run(struct tunnel *tunnel)
{
	pid_t pid;
	int amaster;
	int slave_stderr;

#ifdef HAVE_STRUCT_TERMIOS
	struct termios termp = {
		.c_cflag = B9600,
		.c_cc[VTIME] = 0,
		.c_cc[VMIN] = 1
	};
#endif

	static const char ppp_path[] = PPP_PATH;
	if (access(ppp_path, F_OK) != 0) {
		log_error("%s: %s.\n", ppp_path, strerror(errno));
		return 1;
	}
	log_debug("ppp_path: %s\n", ppp_path);

	slave_stderr = dup(STDERR_FILENO);

	if (slave_stderr < 0) {
		log_error("slave stderr %s\n", strerror(errno));
		return 1;
	}

#ifdef HAVE_STRUCT_TERMIOS
	pid = forkpty(&amaster, NULL, &termp, NULL);
#else
	pid = forkpty(&amaster, NULL, NULL, NULL);
#endif

	if (pid == 0) { // child process

		struct ofv_varr pppd_args = { 0, 0, NULL };

		dup2(slave_stderr, STDERR_FILENO);
		close(slave_stderr);

#if HAVE_USR_SBIN_PPP
		/*
		 * assume there is a default configuration to start.
		 * Support for taking options from the command line
		 * e.g. the name of the configuration or options
		 * to send interactively to ppp will be added later
		 */
		static const char *const v[] = {
			ppp_path,
			"-direct"
		};
		for (unsigned int i = 0; i < ARRAY_SIZE(v); i++)
			if (ofv_append_varr(&pppd_args, v[i]))
				return 1;
#endif
#if HAVE_USR_SBIN_PPPD
		if (tunnel->config->pppd_call) {
			if (ofv_append_varr(&pppd_args, ppp_path))
				return 1;
			if (ofv_append_varr(&pppd_args, "call"))
				return 1;
			if (ofv_append_varr(&pppd_args, tunnel->config->pppd_call))
				return 1;
		} else {
			static const char *const v[] = {
				ppp_path,
				"115200", // speed
				":192.0.2.1", // <local_IP_address>:<remote_IP_address>
				"noipdefault",
				"noaccomp",
				"noauth",
				"default-asyncmap",
				"nopcomp",
				"receive-all",
				"nodefaultroute",
				"nodetach",
				"lcp-max-configure", "40",
				"mru", "1354"
			};
			for (unsigned int i = 0; i < ARRAY_SIZE(v); i++)
				if (ofv_append_varr(&pppd_args, v[i]))
					return 1;
		}
		if (tunnel->config->pppd_use_peerdns)
			if (ofv_append_varr(&pppd_args, "usepeerdns"))
				return 1;
		if (tunnel->config->pppd_log) {
			if (ofv_append_varr(&pppd_args, "debug"))
				return 1;
			if (ofv_append_varr(&pppd_args, "logfile"))
				return 1;
			if (ofv_append_varr(&pppd_args, tunnel->config->pppd_log))
				return 1;
		} else {
			/*
			 * pppd defaults to logging to fd=1, clobbering the
			 * actual PPP data
			 */
			if (ofv_append_varr(&pppd_args, "logfd"))
				return 1;
			if (ofv_append_varr(&pppd_args, "2"))
				return 1;
		}
		if (tunnel->config->pppd_plugin) {
			if (ofv_append_varr(&pppd_args, "plugin"))
				return 1;
			if (ofv_append_varr(&pppd_args, tunnel->config->pppd_plugin))
				return 1;
		}
		if (tunnel->config->pppd_ipparam) {
			if (ofv_append_varr(&pppd_args, "ipparam"))
				return 1;
			if (ofv_append_varr(&pppd_args, tunnel->config->pppd_ipparam))
				return 1;
		}
		if (tunnel->config->pppd_ifname) {
			if (ofv_append_varr(&pppd_args, "ifname"))
				return 1;
			if (ofv_append_varr(&pppd_args, tunnel->config->pppd_ifname))
				return 1;
		}
#endif
#if HAVE_USR_SBIN_PPP
		if (tunnel->config->ppp_system) {
			if (ofv_append_varr(&pppd_args, tunnel->config->ppp_system))
				return 1;
		}
#endif

		close(tunnel->ssl_socket);
		execv(pppd_args.data[0], (char *const *)pppd_args.data);
		free(pppd_args.data);

		fprintf(stderr, "execvp: %s\n", strerror(errno));
		_exit(EXIT_FAILURE);
	} else {
		close(slave_stderr);
		if (pid == -1) {
			log_error("forkpty: %s\n", strerror(errno));
			return 1;
		}
	}

	// Set non-blocking
	int flags = fcntl(amaster, F_GETFL, 0);
	if (flags == -1)
		flags = 0;
	if (fcntl(amaster, F_SETFL, flags | O_NONBLOCK) == -1) {
		log_error("fcntl: %s\n", strerror(errno));
		return 1;
	}

	tunnel->pppd_pid = pid;
	tunnel->pppd_pty = amaster;

	return 0;
}

An issue was discovered in openfortivpn 1.11.0 when used with OpenSSL before 1.0.2. tunnel.c mishandles certificate validation because hostname comparisons do not consider '\0' characters, as demonstrated by a good.example.com\x00evil.example.com attack.


static int on_ppp_if_down(struct tunnel *tunnel)
{
	log_info("Setting ppp interface down.\n");

	if (tunnel->config->set_routes) {
		log_info("Restoring routes...\n");
		ipv4_restore_routes(tunnel);
	}

	if (tunnel->config->set_dns) {
		log_info("Removing VPN nameservers...\n");
		ipv4_del_nameservers_from_resolv_conf(tunnel);
	}

	return 0;
}


int run_tunnel(struct vpn_config *config)
{
	int ret;
	struct tunnel tunnel = {
		.config = config,
		.state = STATE_DOWN,
		.ssl_context = NULL,
		.ssl_handle = NULL,
		.ipv4.ns1_addr.s_addr = 0,
		.ipv4.ns2_addr.s_addr = 0,
		.ipv4.dns_suffix = NULL,
		.on_ppp_if_up = on_ppp_if_up,
		.on_ppp_if_down = on_ppp_if_down
	};

	// Step 0: get gateway host IP
	log_debug("Resolving gateway host ip\n");
	ret = get_gateway_host_ip(&tunnel);
	if (ret)
		goto err_tunnel;

	// Step 1: open a SSL connection to the gateway
	log_debug("Establishing ssl connection\n");
	ret = ssl_connect(&tunnel);
	if (ret)
		goto err_tunnel;
	log_info("Connected to gateway.\n");

	// Step 2: connect to the HTTP interface and authenticate to get a
	// cookie
	ret = auth_log_in(&tunnel);
	if (ret != 1) {
		log_error("Could not authenticate to gateway. Please check the password, client certificate, etc.\n");
		log_debug("%s %d\n", err_http_str(ret), ret);
		ret = 1;
		goto err_tunnel;
	}
	log_info("Authenticated.\n");
	log_debug("Cookie: %s\n", tunnel.cookie);

	ret = auth_request_vpn_allocation(&tunnel);
	if (ret != 1) {
		log_error("VPN allocation request failed (%s).\n",
		          err_http_str(ret));
		ret = 1;
		goto err_tunnel;
	}
	log_info("Remote gateway has allocated a VPN.\n");

	ret = ssl_connect(&tunnel);
	if (ret)
		goto err_tunnel;

	// Step 3: get configuration
	log_debug("Retrieving configuration\n");
	ret = auth_get_config(&tunnel);
	if (ret != 1) {
		log_error("Could not get VPN configuration (%s).\n",
		          err_http_str(ret));
		ret = 1;
		goto err_tunnel;
	}

	// Step 4: run a pppd process
	log_debug("Establishing the tunnel\n");
	ret = pppd_run(&tunnel);
	if (ret)
		goto err_tunnel;

	// Step 5: ask gateway to start tunneling
	log_debug("Switch to tunneling mode\n");
	ret = http_send(&tunnel,
	                "GET /remote/sslvpn-tunnel HTTP/1.1\r\n"
	                "Host: sslvpn\r\n"
	                "Cookie: %s\r\n\r\n",
	                tunnel.cookie);
	if (ret != 1) {
		log_error("Could not start tunnel (%s).\n", err_http_str(ret));
		ret = 1;
		goto err_start_tunnel;
	}

	tunnel.state = STATE_CONNECTING;
	ret = 0;

	// Step 6: perform io between pppd and the gateway, while tunnel is up
	log_debug("Starting IO through the tunnel\n");
	io_loop(&tunnel);

	log_debug("disconnecting\n");
	if (tunnel.state == STATE_UP)
		if (tunnel.on_ppp_if_down != NULL)
			tunnel.on_ppp_if_down(&tunnel);

	tunnel.state = STATE_DISCONNECTING;

err_start_tunnel:
	pppd_terminate(&tunnel);
	log_info("Terminated %s.\n", PPP_DAEMON);
err_tunnel:
	log_info("Closed connection to gateway.\n");
	tunnel.state = STATE_DOWN;

	if (ssl_connect(&tunnel)) {
		log_info("Could not log out.\n");
	} else {
		auth_log_out(&tunnel);
		log_info("Logged out.\n");
	}

	// explicitly free the buffer allocated for split routes of the ipv4 config
	if (tunnel.ipv4.split_rt != NULL) {
		free(tunnel.ipv4.split_rt);
		tunnel.ipv4.split_rt = NULL;
	}
	return ret;
}


void CleanWriters(GF_List *writers)
{
	while (gf_list_count(writers)) {
		TrackWriter *writer = (TrackWriter*)gf_list_get(writers, 0);
		//in case we have an error in the middle of file write, remove our created stco and stsc from sample table
		gf_list_del_item(writer->stbl->child_boxes, writer->stco);
		gf_list_del_item(writer->stbl->child_boxes, writer->stsc);
		gf_isom_box_del(writer->stco);
		gf_isom_box_del((GF_Box *)writer->stsc);
		gf_free(writer);
		gf_list_rem(writers, 0);
	}
}

An issue was discovered in GPAC version 0.8.0 and 1.0.1. There is a use-after-free in the function gf_isom_box_del() in isomedia/box_funcs.c.


GF_Err stbl_GetSampleSize(GF_SampleSizeBox *stsz, u32 SampleNumber, u32 *Size)
{
	if (!stsz || !SampleNumber || (SampleNumber > stsz->sampleCount))
		return GF_BAD_PARAM;

	if (stsz->sampleSize && (stsz->type != GF_ISOM_BOX_TYPE_STZ2)) {
		(*Size) = stsz->sampleSize;
	} else if (stsz->sizes) {
		(*Size) = stsz->sizes[SampleNumber - 1];
	} else {
		(*Size) = 0;
	}
	return GF_OK;
}

Memory leak in the stbl_GetSampleInfos function in MP4Box in GPAC 1.0.1 allows attackers to read memory via a crafted file.


GF_Err stbl_GetSampleRAP(GF_SyncSampleBox *stss, u32 SampleNumber, GF_ISOSAPType *IsRAP, u32 *prevRAP, u32 *nextRAP)
{
	u32 i;
	if (prevRAP) *prevRAP = 0;
	if (nextRAP) *nextRAP = 0;

	(*IsRAP) = RAP_NO;
	if (!stss || !SampleNumber) return GF_BAD_PARAM;

	if (stss->r_LastSyncSample && (stss->r_LastSyncSample < SampleNumber) ) {
		i = stss->r_LastSampleIndex;
	} else {
		i = 0;
	}
	for (; i < stss->nb_entries; i++) {
		//get the entry
		if (stss->sampleNumbers[i] == SampleNumber) {
			//update the cache
			stss->r_LastSyncSample = SampleNumber;
			stss->r_LastSampleIndex = i;
			(*IsRAP) = RAP;
		}
		else if (stss->sampleNumbers[i] > SampleNumber) {
			if (nextRAP) *nextRAP = stss->sampleNumbers[i];
			return GF_OK;
		}
		if (prevRAP) *prevRAP = stss->sampleNumbers[i];
	}
	return GF_OK;
}


void GetGhostNum(GF_StscEntry *ent, u32 EntryIndex, u32 count, GF_SampleTableBox *stbl)
{
	GF_StscEntry *nextEnt;
	u32 ghostNum = 1;

	if (!ent) {
		stbl->SampleToChunk->ghostNumber = 0;
		return;
	}

	if (!ent->nextChunk) {
		if (EntryIndex+1 == count) {
			//not specified in the spec, what if the last sample to chunk is no written?
			if (stbl->ChunkOffset->type == GF_ISOM_BOX_TYPE_STCO) {
				GF_ChunkOffsetBox *stco = (GF_ChunkOffsetBox *)stbl->ChunkOffset;
				ghostNum = (stco->nb_entries > ent->firstChunk) ? (1 + stco->nb_entries - ent->firstChunk) : 1;
			} else {
				GF_ChunkLargeOffsetBox *co64 = (GF_ChunkLargeOffsetBox *)stbl->ChunkOffset;
				ghostNum = (co64->nb_entries > ent->firstChunk) ? (1 + co64->nb_entries - ent->firstChunk) : 1;
			}
		} else {
			//this is an unknown case due to edit mode...
			nextEnt = &stbl->SampleToChunk->entries[EntryIndex+1];
			ghostNum = nextEnt->firstChunk - ent->firstChunk;
		}
	} else {
		ghostNum = (ent->nextChunk > ent->firstChunk) ? (ent->nextChunk - ent->firstChunk) : 1;
	}
	stbl->SampleToChunk->ghostNumber = ghostNum;
}


GF_Err stbl_GetSampleInfos(GF_SampleTableBox *stbl, u32 sampleNumber, u64 *offset, u32 *chunkNumber, u32 *descIndex, GF_StscEntry **out_ent)
{
	GF_Err e;
	u32 i, k, offsetInChunk, size, chunk_num;
	GF_ChunkOffsetBox *stco;
	GF_ChunkLargeOffsetBox *co64;
	GF_StscEntry *ent;

	(*offset) = 0;
	(*chunkNumber) = (*descIndex) = 0;
	if (out_ent) (*out_ent) = NULL;
	if (!stbl || !sampleNumber) return GF_BAD_PARAM;
	if (!stbl->ChunkOffset || !stbl->SampleToChunk || !stbl->SampleSize) return GF_ISOM_INVALID_FILE;

	if (stbl->SampleSize && stbl->SampleToChunk->nb_entries == stbl->SampleSize->sampleCount) {
		ent = &stbl->SampleToChunk->entries[sampleNumber-1];
		if (!ent) return GF_BAD_PARAM;
		(*descIndex) = ent->sampleDescriptionIndex;
		(*chunkNumber) = sampleNumber;
		if (out_ent) *out_ent = ent;
		if ( stbl->ChunkOffset->type == GF_ISOM_BOX_TYPE_STCO) {
			stco = (GF_ChunkOffsetBox *)stbl->ChunkOffset;
			if (!stco->offsets) return GF_ISOM_INVALID_FILE;
			if (stco->nb_entries < sampleNumber) return GF_ISOM_INVALID_FILE;

			(*offset) = (u64) stco->offsets[sampleNumber - 1];
		} else {
			co64 = (GF_ChunkLargeOffsetBox *)stbl->ChunkOffset;
			if (!co64->offsets) return GF_ISOM_INVALID_FILE;
			if (co64->nb_entries < sampleNumber) return GF_ISOM_INVALID_FILE;

			(*offset) = co64->offsets[sampleNumber - 1];
		}
		return GF_OK;
	}

	//check our cache: if desired sample is at or above current cache entry, start from here
	if (stbl->SampleToChunk->firstSampleInCurrentChunk &&
	        (stbl->SampleToChunk->firstSampleInCurrentChunk <= sampleNumber)) {

		i = stbl->SampleToChunk->currentIndex;
		ent = &stbl->SampleToChunk->entries[stbl->SampleToChunk->currentIndex];
		GetGhostNum(ent, i, stbl->SampleToChunk->nb_entries, stbl);
		k = stbl->SampleToChunk->currentChunk;
	}
	//otherwise start from first entry
	else {
		i = 0;
		stbl->SampleToChunk->currentIndex = 0;
		stbl->SampleToChunk->currentChunk = 1;
		stbl->SampleToChunk->ghostNumber = 1;
		stbl->SampleToChunk->firstSampleInCurrentChunk = 1;
		ent = &stbl->SampleToChunk->entries[0];
		GetGhostNum(ent, 0, stbl->SampleToChunk->nb_entries, stbl);
		k = stbl->SampleToChunk->currentChunk;
	}

	//first get the chunk
	for (; i < stbl->SampleToChunk->nb_entries; i++) {
		assert(stbl->SampleToChunk->firstSampleInCurrentChunk <= sampleNumber);
		//corrupted file (less sample2chunk info than sample count
		if (k > stbl->SampleToChunk->ghostNumber) {
			return GF_ISOM_INVALID_FILE;
		}

		//check if sample is in current chunk
		u32 max_chunks_in_entry = stbl->SampleToChunk->ghostNumber - k;
		u32 nb_chunks_for_sample = sampleNumber - stbl->SampleToChunk->firstSampleInCurrentChunk;
		if (ent->samplesPerChunk)
			nb_chunks_for_sample /= ent->samplesPerChunk;

		if (
			(nb_chunks_for_sample <= max_chunks_in_entry)
			&& (stbl->SampleToChunk->firstSampleInCurrentChunk + (nb_chunks_for_sample+1) * ent->samplesPerChunk > sampleNumber)
		) {

			stbl->SampleToChunk->firstSampleInCurrentChunk += nb_chunks_for_sample * ent->samplesPerChunk;
			stbl->SampleToChunk->currentChunk += nb_chunks_for_sample;
			goto sample_found;
		}
		max_chunks_in_entry += 1;
		stbl->SampleToChunk->firstSampleInCurrentChunk += max_chunks_in_entry * ent->samplesPerChunk;
		stbl->SampleToChunk->currentChunk += max_chunks_in_entry;

		//not in this entry, get the next entry if not the last one
		if (i+1 != stbl->SampleToChunk->nb_entries) {
			ent = &stbl->SampleToChunk->entries[i+1];
			//update the GhostNumber
			GetGhostNum(ent, i+1, stbl->SampleToChunk->nb_entries, stbl);
			//update the entry in our cache
			stbl->SampleToChunk->currentIndex = i+1;
			stbl->SampleToChunk->currentChunk = 1;
			k = 1;
		}
	}
	//if we get here, gasp, the sample was not found
	return GF_ISOM_INVALID_FILE;

sample_found:

	(*descIndex) = ent->sampleDescriptionIndex;
	(*chunkNumber) = chunk_num = ent->firstChunk + stbl->SampleToChunk->currentChunk - 1;
	if (out_ent) *out_ent = ent;
	if (! *chunkNumber)
		return GF_ISOM_INVALID_FILE;

	//ok, get the size of all the previous samples in the chunk
	offsetInChunk = 0;
	//constant size
	if (stbl->SampleSize && stbl->SampleSize->sampleSize) {
		u32 diff = sampleNumber - stbl->SampleToChunk->firstSampleInCurrentChunk;
		offsetInChunk += diff * stbl->SampleSize->sampleSize;
	} else if ((stbl->r_last_chunk_num == chunk_num) && (stbl->r_last_sample_num == sampleNumber)) {
		offsetInChunk = stbl->r_last_offset_in_chunk;
	} else if ((stbl->r_last_chunk_num == chunk_num) && (stbl->r_last_sample_num + 1 == sampleNumber)) {
		e = stbl_GetSampleSize(stbl->SampleSize, stbl->r_last_sample_num, &size);
		if (e) return e;
		stbl->r_last_offset_in_chunk += size;
		stbl->r_last_sample_num = sampleNumber;
		offsetInChunk = stbl->r_last_offset_in_chunk;
	} else {
		//warning, firstSampleInChunk is at least 1 - not 0
		for (i = stbl->SampleToChunk->firstSampleInCurrentChunk; i < sampleNumber; i++) {
			e = stbl_GetSampleSize(stbl->SampleSize, i, &size);
			if (e) return e;
			offsetInChunk += size;
		}
		stbl->r_last_chunk_num = chunk_num;
		stbl->r_last_sample_num = sampleNumber;
		stbl->r_last_offset_in_chunk = offsetInChunk;
	}
	//OK, that's the size of our offset in the chunk
	//now get the chunk
	if ( stbl->ChunkOffset->type == GF_ISOM_BOX_TYPE_STCO) {
		stco = (GF_ChunkOffsetBox *)stbl->ChunkOffset;
		if (stco->nb_entries < (*chunkNumber) ) return GF_ISOM_INVALID_FILE;
		(*offset) = (u64) stco->offsets[(*chunkNumber) - 1] + (u64) offsetInChunk;
	} else {
		co64 = (GF_ChunkLargeOffsetBox *)stbl->ChunkOffset;
		if (co64->nb_entries < (*chunkNumber) ) return GF_ISOM_INVALID_FILE;
		(*offset) = co64->offsets[(*chunkNumber) - 1] + (u64) offsetInChunk;
	}
	return GF_OK;
}


GF_Err stbl_GetSampleDTS_and_Duration(GF_TimeToSampleBox *stts, u32 SampleNumber, u64 *DTS, u32 *duration)
{
	u32 i, j, count;
	GF_SttsEntry *ent;

	(*DTS) = 0;
	if (duration) {
		*duration = 0;
	}
	if (!stts || !SampleNumber) return GF_BAD_PARAM;

	ent = NULL;
	//use our cache
	count = stts->nb_entries;
	if (stts->r_FirstSampleInEntry
	        && (stts->r_FirstSampleInEntry <= SampleNumber)
	        //this is for read/write access
	        && (stts->r_currentEntryIndex < count) ) {

		i = stts->r_currentEntryIndex;
	} else {
		i = stts->r_currentEntryIndex = 0;
		stts->r_FirstSampleInEntry = 1;
		stts->r_CurrentDTS = 0;
	}

	for (; i < count; i++) {
		ent = &stts->entries[i];

		//in our entry
		if (ent->sampleCount + stts->r_FirstSampleInEntry >= 1 + SampleNumber) {
			j = SampleNumber - stts->r_FirstSampleInEntry;
			goto found;
		}

		//update our cache
		stts->r_CurrentDTS += (u64)ent->sampleCount * ent->sampleDelta;
		stts->r_currentEntryIndex += 1;
		stts->r_FirstSampleInEntry += ent->sampleCount;
	}
//	if (SampleNumber >= stts->r_FirstSampleInEntry + ent->sampleCount) return GF_BAD_PARAM;

	//no ent, this is really weird. Let's assume the DTS is then what is written in the table
	if (!ent || (i == count)) {
		(*DTS) = stts->r_CurrentDTS;
		if (duration) *duration = ent ? ent->sampleDelta : 0;
	}
	return GF_OK;

found:
	(*DTS) = stts->r_CurrentDTS + j * (u64) ent->sampleDelta;
	if (duration) *duration = ent->sampleDelta;
	return GF_OK;
}


GF_Err stbl_findEntryForTime(GF_SampleTableBox *stbl, u64 DTS, u8 useCTS, u32 *sampleNumber, u32 *prevSampleNumber)
{
	u32 i, j, curSampNum, count;
	s32 CTSOffset;
	u64 curDTS;
	GF_SttsEntry *ent;
	(*sampleNumber) = 0;
	(*prevSampleNumber) = 0;

	if (!stbl->TimeToSample) return GF_ISOM_INVALID_FILE;

	/*CTS is ALWAYS disabled for now to make sure samples are fetched in decoding order. useCTS is therefore disabled*/
#if 0
	if (!stbl->CompositionOffset) useCTS = 0;
#endif

	//our cache
	if (stbl->TimeToSample->r_FirstSampleInEntry &&
	        (DTS >= stbl->TimeToSample->r_CurrentDTS) ) {
		//if we're using CTS, we don't really know whether we're in the good entry or not
		//(eg, the real DTS of the sample could be in a previous entry
		i = stbl->TimeToSample->r_currentEntryIndex;
		curDTS = stbl->TimeToSample->r_CurrentDTS;
		curSampNum = stbl->TimeToSample->r_FirstSampleInEntry;
	} else {
		i = 0;
		curDTS = stbl->TimeToSample->r_CurrentDTS = 0;
		curSampNum = stbl->TimeToSample->r_FirstSampleInEntry = 1;
		stbl->TimeToSample->r_currentEntryIndex = 0;
	}

#if 0
	//we need to validate our cache if we are using CTS because of B-frames and co...
	if (i && useCTS) {
		while (1) {
			stbl_GetSampleCTS(stbl->CompositionOffset, curSampNum, &CTSOffset);
			//we're too far, rewind
			if ( i && (curDTS + CTSOffset > DTS) ) {
				ent = &stbl->TimeToSample->entries[i];
				curSampNum -= ent->sampleCount;
				curDTS -= (u64)ent->sampleDelta * ent->sampleCount;
				i --;
			} else if (!i) {
				//beginning of the table, no choice
				curDTS = stbl->TimeToSample->r_CurrentDTS = 0;
				curSampNum = stbl->TimeToSample->r_FirstSampleInEntry = 1;
				stbl->TimeToSample->r_currentEntryIndex = 0;
				break;
			} else {
				//OK now we're good
				break;
			}
		}
	}
#endif

	//look for the DTS from this entry
	count = stbl->TimeToSample->nb_entries;
	for (; i<count; i++) {
		ent = &stbl->TimeToSample->entries[i];
#if 0
		if (useCTS) {
			stbl_GetSampleCTS(stbl->CompositionOffset, curSampNum, &CTSOffset);
		} else
#endif
		{
			CTSOffset = 0;
		}
		for (j=0; j<ent->sampleCount; j++) {
			if (curDTS + CTSOffset >= DTS) goto entry_found;
			curSampNum += 1;
			curDTS += ent->sampleDelta;
		}
		//we're switching to the next entry, update the cache!
		stbl->TimeToSample->r_CurrentDTS += (u64)ent->sampleCount * ent->sampleDelta;
		stbl->TimeToSample->r_currentEntryIndex += 1;
		stbl->TimeToSample->r_FirstSampleInEntry += ent->sampleCount;
	}
	//return as is
	return GF_OK;

entry_found:
	//do we have the exact time ?
	if (curDTS + CTSOffset == DTS) {
		(*sampleNumber) = curSampNum;
	}
	//if we match the exact DTS also select this sample
	else if (curDTS == DTS) {
		(*sampleNumber) = curSampNum;
	} else {
		//exception for the first sample (we need to "load" the playback)
		if (curSampNum != 1) {
			(*prevSampleNumber) = curSampNum - 1;
		} else {
			(*prevSampleNumber) = 1;
		}
	}
	return GF_OK;
}


GF_Err stbl_GetSampleCTS(GF_CompositionOffsetBox *ctts, u32 SampleNumber, s32 *CTSoffset)
{
	u32 i;

	(*CTSoffset) = 0;
	//test on SampleNumber is done before
	if (!ctts || !SampleNumber) return GF_BAD_PARAM;

	if (ctts->r_FirstSampleInEntry && (ctts->r_FirstSampleInEntry < SampleNumber) ) {
		i = ctts->r_currentEntryIndex;
	} else {
		ctts->r_FirstSampleInEntry = 1;
		ctts->r_currentEntryIndex = 0;
		i = 0;
	}
	for (; i< ctts->nb_entries; i++) {
		if (SampleNumber < ctts->r_FirstSampleInEntry + ctts->entries[i].sampleCount) break;
		//update our cache
		ctts->r_currentEntryIndex += 1;
		ctts->r_FirstSampleInEntry += ctts->entries[i].sampleCount;
	}
	//no ent, set everything to 0...
	if (i==ctts->nb_entries) return GF_OK;
	/*asked for a sample not in table - this means CTTS is 0 (that's due to out internal packing construction of CTTS)*/
	if (SampleNumber >= ctts->r_FirstSampleInEntry + ctts->entries[i].sampleCount) return GF_OK;
	(*CTSoffset) = ctts->entries[i].decodingOffset;
	return GF_OK;
}


GF_Err stbl_GetSampleDTS(GF_TimeToSampleBox *stts, u32 SampleNumber, u64 *DTS)
{
	return stbl_GetSampleDTS_and_Duration(stts, SampleNumber, DTS, NULL);
}


GF_Err stbl_GetSampleShadow(GF_ShadowSyncBox *stsh, u32 *sampleNumber, u32 *syncNum)
{
	u32 i, count;
	GF_StshEntry *ent;

	if (stsh->r_LastFoundSample && (stsh->r_LastFoundSample <= *sampleNumber)) {
		i = stsh->r_LastEntryIndex;
	} else {
		i = 0;
		stsh->r_LastFoundSample = 1;
	}

	ent = NULL;
	(*syncNum) = 0;

	count = gf_list_count(stsh->entries);
	for (; i<count; i++) {
		ent = (GF_StshEntry*)gf_list_get(stsh->entries, i);
		//we get the exact desired sample !
		if (ent->shadowedSampleNumber == *sampleNumber) {
			(*syncNum) = ent->syncSampleNumber;
			stsh->r_LastFoundSample = *sampleNumber;
			stsh->r_LastEntryIndex = i;
			return GF_OK;
		} else if (ent->shadowedSampleNumber > *sampleNumber) {
			//do we have an entry before ? If not, there is no shadowing available
			//for this sample
			if (!i) return GF_OK;
			//ok, indicate the previous ShadowedSample
			ent = (GF_StshEntry*)gf_list_get(stsh->entries, i-1);
			(*syncNum) = ent->syncSampleNumber;
			//change the sample number
			(*sampleNumber) = ent->shadowedSampleNumber;
			//reset the cache to the last ShadowedSample
			stsh->r_LastEntryIndex = i-1;
			stsh->r_LastFoundSample = ent->shadowedSampleNumber;
		}
	}
	stsh->r_LastEntryIndex = i-1;
	stsh->r_LastFoundSample = ent ? ent->shadowedSampleNumber : 0;
	return GF_OK;
}


GF_Err stbl_GetPaddingBits(GF_PaddingBitsBox *padb, u32 SampleNumber, u8 *PadBits)
{
	if (!PadBits) return GF_BAD_PARAM;
	*PadBits = 0;
	if (!padb || !padb->padbits) return GF_OK;
	//the spec says "should" not shall. return 0 padding
	if (padb->SampleCount < SampleNumber) return GF_OK;
	*PadBits = padb->padbits[SampleNumber-1];
	return GF_OK;
}


GF_Err stbl_GetSampleDepType(GF_SampleDependencyTypeBox *sdep, u32 SampleNumber, u32 *isLeading, u32 *dependsOn, u32 *dependedOn, u32 *redundant)
{
	u8 flag;

	assert(dependsOn && dependedOn && redundant);
	*dependsOn = *dependedOn = *redundant = 0;

	if (SampleNumber > sdep->sampleCount) {
		return GF_OK;
	}
	flag = sdep->sample_info[SampleNumber-1];
	*isLeading = (flag >> 6) & 3;
	*dependsOn = (flag >> 4) & 3;
	*dependedOn = (flag >> 2) & 3;
	*redundant = (flag) & 3;
	return GF_OK;
}


GF_Err stbl_SearchSAPs(GF_SampleTableBox *stbl, u32 SampleNumber, GF_ISOSAPType *IsRAP, u32 *prevRAP, u32 *nextRAP)
{
	u32 i, j, count, count2;
	assert(prevRAP);
	assert(nextRAP);
	(*prevRAP) = 0;
	(*nextRAP) = 0;
	(*IsRAP) = RAP_NO;

	if (!stbl->sampleGroups || !stbl->sampleGroupsDescription) return GF_OK;

	count = gf_list_count(stbl->sampleGroups);
	count2 = gf_list_count(stbl->sampleGroupsDescription);
	for (i=0; i<count; i++) {
		GF_SampleGroupDescriptionBox *sgdp = NULL;
		Bool is_rap_group = 0;
		s32 roll_distance = 0;
		u32 first_sample_in_entry, last_sample_in_entry;
		GF_SampleGroupBox *sg = gf_list_get(stbl->sampleGroups, i);
		switch (sg->grouping_type) {
		case GF_ISOM_SAMPLE_GROUP_RAP:
		case GF_ISOM_SAMPLE_GROUP_SYNC:
			is_rap_group = 1;
			break;
		case GF_ISOM_SAMPLE_GROUP_ROLL:
			break;
		default:
			continue;
		}
		for (j=0; j<count2; j++) {
			sgdp = gf_list_get(stbl->sampleGroupsDescription, j);
			if (sgdp->grouping_type==sg->grouping_type) break;
			sgdp = NULL;
		}
		if (! sgdp) continue;

		first_sample_in_entry=1;
		for (j=0; j<sg->entry_count; j++) {
			u32 first_rap_in_entry, last_rap_in_entry;
			last_sample_in_entry = first_sample_in_entry + sg->sample_entries[j].sample_count - 1;

			/*samples in this entry are not RAPs, continue*/
			if (! sg->sample_entries[j].group_description_index) {
				first_sample_in_entry += sg->sample_entries[j].sample_count;
				continue;
			}
			if (!is_rap_group) {
				GF_RollRecoveryEntry *entry = gf_list_get(sgdp->group_descriptions, sg->sample_entries[j].group_description_index - 1);
				roll_distance = entry ? entry->roll_distance : 0;
			}

			/*we consider the first sample in a roll or rap group entry to be the RAP (eg, we have to decode from this sample anyway)
			except if roll_distance is strictly negative in which case we have to rewind our sample numbers from roll_distance*/
			if (roll_distance < 0) {
				if ((s32) first_sample_in_entry + roll_distance>=0) first_rap_in_entry = first_sample_in_entry + roll_distance;
				else first_rap_in_entry = 0;

				if ((s32) last_sample_in_entry + roll_distance>=0) last_rap_in_entry = last_sample_in_entry + roll_distance;
				else last_rap_in_entry = 0;
			} else {
				first_rap_in_entry = first_sample_in_entry;
				last_rap_in_entry = last_sample_in_entry;
			}

			/*store previous & next sample RAP - note that we do not store the closest previous RAP, only the first of the previous RAP group
			as RAPs are usually isolated this should not be an issue*/
			if (first_rap_in_entry <= SampleNumber) {
				*prevRAP = first_rap_in_entry;
			}
			*nextRAP = last_rap_in_entry;

			/*sample lies in this (rap) group, it is rap*/
			if (is_rap_group) {
				if ((first_rap_in_entry <= SampleNumber) && (SampleNumber <= last_rap_in_entry)) {
					(*IsRAP) = RAP;
					return GF_OK;
				}
			} else {
				/*prevRAP or nextRAP matches SampleNumber, sample is RAP*/
				if ((*prevRAP == SampleNumber) || (*nextRAP == SampleNumber)) {
					(*IsRAP) = RAP;
					return GF_OK;
				}
			}

			/*first sample in entry is after our target sample, abort*/
			if (first_rap_in_entry > SampleNumber) {
				break;
			}
			first_sample_in_entry += sg->sample_entries[j].sample_count;
		}
	}
	return GF_OK;
}


Fix a dangerous integer overflow and a malloc of negative size.

PiperOrigin-RevId: 371254154
Change-Id: I250a98a3df26328770167025670235a963a72da0

int TfLiteFloatArrayGetSizeInBytes(int size) {
  static TfLiteFloatArray dummy;
  return sizeof(dummy) + sizeof(dummy.data[0]) * size;
}

TensorFlow is an end-to-end open source platform for machine learning. The TFLite code for allocating `TFLiteIntArray`s is vulnerable to an integer overflow issue(https://github.com/tensorflow/tensorflow/blob/4ceffae632721e52bf3501b736e4fe9d1221cdfa/tensorflow/lite/c/common.c#L24-L27). An attacker can craft a model such that the `size` multiplier is so large that the return value overflows the `int` datatype and becomes negative. In turn, this results in invalid value being given to `malloc`(https://github.com/tensorflow/tensorflow/blob/4ceffae632721e52bf3501b736e4fe9d1221cdfa/tensorflow/lite/c/common.c#L47-L52). In this case, `ret->size` would dereference an invalid pointer. 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.


int TfLiteIntArrayGetSizeInBytes(int size) {
  static TfLiteIntArray dummy;
  return sizeof(dummy) + sizeof(dummy.data[0]) * size;
}


void TfLiteTensorRealloc(size_t num_bytes, TfLiteTensor* tensor) {
  if (tensor->allocation_type != kTfLiteDynamic &&
      tensor->allocation_type != kTfLitePersistentRo) {
    return;
  }
  // TODO(b/145340303): Tensor data should be aligned.
  if (!tensor->data.raw) {
    tensor->data.raw = malloc(num_bytes);
  } else if (num_bytes > tensor->bytes) {
    tensor->data.raw = realloc(tensor->data.raw, num_bytes);
  }
  tensor->bytes = num_bytes;
}


TfLiteDelegate TfLiteDelegateCreate() {
  TfLiteDelegate d = {
      .data_ = NULL,
      .Prepare = NULL,
      .CopyFromBufferHandle = NULL,
      .CopyToBufferHandle = NULL,
      .FreeBufferHandle = NULL,
      .flags = kTfLiteDelegateFlagsNone,
  };
  return d;
}


nbd/client: fix drop_sync [CVE-2017-2630]

Comparison symbol is misused. It may lead to memory corruption.
Introduced in commit 7d3123e.

Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
Message-Id: <20170203154757.36140-6-vsementsov@virtuozzo.com>
[eblake: add CVE details, update conditional]
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-AndrÃ© Lureau <marcandre.lureau@redhat.com>
Message-Id: <20170307151627.27212-1-eblake@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

int nbd_disconnect(int fd)
{
    ioctl(fd, NBD_CLEAR_QUE);
    ioctl(fd, NBD_DISCONNECT);
    ioctl(fd, NBD_CLEAR_SOCK);
    return 0;
}

A stack buffer overflow flaw was found in the Quick Emulator (QEMU) before 2.9 built with the Network Block Device (NBD) client support. The flaw could occur while processing server's response to a 'NBD_OPT_LIST' request. A malicious NBD server could use this issue to crash a remote NBD client resulting in DoS or potentially execute arbitrary code on client host with privileges of the QEMU process.


static int nbd_errno_to_system_errno(int err)
{
    int ret;
    switch (err) {
    case NBD_SUCCESS:
        ret = 0;
        break;
    case NBD_EPERM:
        ret = EPERM;
        break;
    case NBD_EIO:
        ret = EIO;
        break;
    case NBD_ENOMEM:
        ret = ENOMEM;
        break;
    case NBD_ENOSPC:
        ret = ENOSPC;
        break;
    case NBD_ESHUTDOWN:
        ret = ESHUTDOWN;
        break;
    default:
        TRACE("Squashing unexpected error %d to EINVAL", err);
        /* fallthrough */
    case NBD_EINVAL:
        ret = EINVAL;
        break;
    }
    return ret;
}


int nbd_init(int fd, QIOChannelSocket *sioc, uint16_t flags, off_t size)
{
    unsigned long sectors = size / BDRV_SECTOR_SIZE;
    if (size / BDRV_SECTOR_SIZE != sectors) {
        LOG("Export size %lld too large for 32-bit kernel", (long long) size);
        return -E2BIG;
    }

    TRACE("Setting NBD socket");

    if (ioctl(fd, NBD_SET_SOCK, (unsigned long) sioc->fd) < 0) {
        int serrno = errno;
        LOG("Failed to set NBD socket");
        return -serrno;
    }

    TRACE("Setting block size to %lu", (unsigned long)BDRV_SECTOR_SIZE);

    if (ioctl(fd, NBD_SET_BLKSIZE, (unsigned long)BDRV_SECTOR_SIZE) < 0) {
        int serrno = errno;
        LOG("Failed setting NBD block size");
        return -serrno;
    }

    TRACE("Setting size to %lu block(s)", sectors);
    if (size % BDRV_SECTOR_SIZE) {
        TRACE("Ignoring trailing %d bytes of export",
              (int) (size % BDRV_SECTOR_SIZE));
    }

    if (ioctl(fd, NBD_SET_SIZE_BLOCKS, sectors) < 0) {
        int serrno = errno;
        LOG("Failed setting size (in blocks)");
        return -serrno;
    }

    if (ioctl(fd, NBD_SET_FLAGS, (unsigned long) flags) < 0) {
        if (errno == ENOTTY) {
            int read_only = (flags & NBD_FLAG_READ_ONLY) != 0;
            TRACE("Setting readonly attribute");

            if (ioctl(fd, BLKROSET, (unsigned long) &read_only) < 0) {
                int serrno = errno;
                LOG("Failed setting read-only attribute");
                return -serrno;
            }
        } else {
            int serrno = errno;
            LOG("Failed setting flags");
            return -serrno;
        }
    }

    TRACE("Negotiation ended");

    return 0;
}


static int nbd_send_option_request(QIOChannel *ioc, uint32_t opt,
                                   uint32_t len, const char *data,
                                   Error **errp)
{
    nbd_option req;
    QEMU_BUILD_BUG_ON(sizeof(req) != 16);

    if (len == -1) {
        req.length = len = strlen(data);
    }
    TRACE("Sending option request %" PRIu32", len %" PRIu32, opt, len);

    stq_be_p(&req.magic, NBD_OPTS_MAGIC);
    stl_be_p(&req.option, opt);
    stl_be_p(&req.length, len);

    if (write_sync(ioc, &req, sizeof(req)) != sizeof(req)) {
        error_setg(errp, "Failed to send option request header");
        return -1;
    }

    if (len && write_sync(ioc, (char *) data, len) != len) {
        error_setg(errp, "Failed to send option request data");
        return -1;
    }

    return 0;
}


int nbd_client(int fd)
{
    return -ENOTSUP;
}


static int nbd_receive_option_reply(QIOChannel *ioc, uint32_t opt,
                                    nbd_opt_reply *reply, Error **errp)
{
    QEMU_BUILD_BUG_ON(sizeof(*reply) != 20);
    if (read_sync(ioc, reply, sizeof(*reply)) != sizeof(*reply)) {
        error_setg(errp, "failed to read option reply");
        nbd_send_opt_abort(ioc);
        return -1;
    }
    be64_to_cpus(&reply->magic);
    be32_to_cpus(&reply->option);
    be32_to_cpus(&reply->type);
    be32_to_cpus(&reply->length);

    TRACE("Received option reply %" PRIx32", type %" PRIx32", len %" PRIu32,
          reply->option, reply->type, reply->length);

    if (reply->magic != NBD_REP_MAGIC) {
        error_setg(errp, "Unexpected option reply magic");
        nbd_send_opt_abort(ioc);
        return -1;
    }
    if (reply->option != opt) {
        error_setg(errp, "Unexpected option type %x expected %x",
                   reply->option, opt);
        nbd_send_opt_abort(ioc);
        return -1;
    }
    return 0;
}


static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
                                        QCryptoTLSCreds *tlscreds,
                                        const char *hostname, Error **errp)
{
    nbd_opt_reply reply;
    QIOChannelTLS *tioc;
    struct NBDTLSHandshakeData data = { 0 };

    TRACE("Requesting TLS from server");
    if (nbd_send_option_request(ioc, NBD_OPT_STARTTLS, 0, NULL, errp) < 0) {
        return NULL;
    }

    TRACE("Getting TLS reply from server");
    if (nbd_receive_option_reply(ioc, NBD_OPT_STARTTLS, &reply, errp) < 0) {
        return NULL;
    }

    if (reply.type != NBD_REP_ACK) {
        error_setg(errp, "Server rejected request to start TLS %" PRIx32,
                   reply.type);
        nbd_send_opt_abort(ioc);
        return NULL;
    }

    if (reply.length != 0) {
        error_setg(errp, "Start TLS response was not zero %" PRIu32,
                   reply.length);
        nbd_send_opt_abort(ioc);
        return NULL;
    }

    TRACE("TLS request approved, setting up TLS");
    tioc = qio_channel_tls_new_client(ioc, tlscreds, hostname, errp);
    if (!tioc) {
        return NULL;
    }
    qio_channel_set_name(QIO_CHANNEL(tioc), "nbd-client-tls");
    data.loop = g_main_loop_new(g_main_context_default(), FALSE);
    TRACE("Starting TLS handshake");
    qio_channel_tls_handshake(tioc,
                              nbd_tls_handshake,
                              &data,
                              NULL);

    if (!data.complete) {
        g_main_loop_run(data.loop);
    }
    g_main_loop_unref(data.loop);
    if (data.error) {
        error_propagate(errp, data.error);
        object_unref(OBJECT(tioc));
        return NULL;
    }

    return QIO_CHANNEL(tioc);
}


int nbd_init(int fd, QIOChannelSocket *ioc, uint16_t flags, off_t size)
{
    return -ENOTSUP;
}


static int nbd_receive_list(QIOChannel *ioc, const char *want, bool *match,
                            Error **errp)
{
    nbd_opt_reply reply;
    uint32_t len;
    uint32_t namelen;
    char name[NBD_MAX_NAME_SIZE + 1];
    int error;

    if (nbd_receive_option_reply(ioc, NBD_OPT_LIST, &reply, errp) < 0) {
        return -1;
    }
    error = nbd_handle_reply_err(ioc, &reply, errp);
    if (error <= 0) {
        /* The server did not support NBD_OPT_LIST, so set *match on
         * the assumption that any name will be accepted.  */
        *match = true;
        return error;
    }
    len = reply.length;

    if (reply.type == NBD_REP_ACK) {
        if (len != 0) {
            error_setg(errp, "length too long for option end");
            nbd_send_opt_abort(ioc);
            return -1;
        }
        return 0;
    } else if (reply.type != NBD_REP_SERVER) {
        error_setg(errp, "Unexpected reply type %" PRIx32 " expected %x",
                   reply.type, NBD_REP_SERVER);
        nbd_send_opt_abort(ioc);
        return -1;
    }

    if (len < sizeof(namelen) || len > NBD_MAX_BUFFER_SIZE) {
        error_setg(errp, "incorrect option length %" PRIu32, len);
        nbd_send_opt_abort(ioc);
        return -1;
    }
    if (read_sync(ioc, &namelen, sizeof(namelen)) != sizeof(namelen)) {
        error_setg(errp, "failed to read option name length");
        nbd_send_opt_abort(ioc);
        return -1;
    }
    namelen = be32_to_cpu(namelen);
    len -= sizeof(namelen);
    if (len < namelen) {
        error_setg(errp, "incorrect option name length");
        nbd_send_opt_abort(ioc);
        return -1;
    }
    if (namelen != strlen(want)) {
        if (drop_sync(ioc, len) != len) {
            error_setg(errp, "failed to skip export name with wrong length");
            nbd_send_opt_abort(ioc);
            return -1;
        }
        return 1;
    }

    assert(namelen < sizeof(name));
    if (read_sync(ioc, name, namelen) != namelen) {
        error_setg(errp, "failed to read export name");
        nbd_send_opt_abort(ioc);
        return -1;
    }
    name[namelen] = '\0';
    len -= namelen;
    if (drop_sync(ioc, len) != len) {
        error_setg(errp, "failed to read export description");
        nbd_send_opt_abort(ioc);
        return -1;
    }
    if (!strcmp(name, want)) {
        *match = true;
    }
    return 1;
}


int nbd_disconnect(int fd)
{
    return -ENOTSUP;
}


int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint16_t *flags,
                          QCryptoTLSCreds *tlscreds, const char *hostname,
                          QIOChannel **outioc,
                          off_t *size, Error **errp)
{
    char buf[256];
    uint64_t magic, s;
    int rc;
    bool zeroes = true;

    TRACE("Receiving negotiation tlscreds=%p hostname=%s.",
          tlscreds, hostname ? hostname : "<null>");

    rc = -EINVAL;

    if (outioc) {
        *outioc = NULL;
    }
    if (tlscreds && !outioc) {
        error_setg(errp, "Output I/O channel required for TLS");
        goto fail;
    }

    if (read_sync(ioc, buf, 8) != 8) {
        error_setg(errp, "Failed to read data");
        goto fail;
    }

    buf[8] = '\0';
    if (strlen(buf) == 0) {
        error_setg(errp, "Server connection closed unexpectedly");
        goto fail;
    }

    TRACE("Magic is %c%c%c%c%c%c%c%c",
          qemu_isprint(buf[0]) ? buf[0] : '.',
          qemu_isprint(buf[1]) ? buf[1] : '.',
          qemu_isprint(buf[2]) ? buf[2] : '.',
          qemu_isprint(buf[3]) ? buf[3] : '.',
          qemu_isprint(buf[4]) ? buf[4] : '.',
          qemu_isprint(buf[5]) ? buf[5] : '.',
          qemu_isprint(buf[6]) ? buf[6] : '.',
          qemu_isprint(buf[7]) ? buf[7] : '.');

    if (memcmp(buf, "NBDMAGIC", 8) != 0) {
        error_setg(errp, "Invalid magic received");
        goto fail;
    }

    if (read_sync(ioc, &magic, sizeof(magic)) != sizeof(magic)) {
        error_setg(errp, "Failed to read magic");
        goto fail;
    }
    magic = be64_to_cpu(magic);
    TRACE("Magic is 0x%" PRIx64, magic);

    if (magic == NBD_OPTS_MAGIC) {
        uint32_t clientflags = 0;
        uint16_t globalflags;
        bool fixedNewStyle = false;

        if (read_sync(ioc, &globalflags, sizeof(globalflags)) !=
            sizeof(globalflags)) {
            error_setg(errp, "Failed to read server flags");
            goto fail;
        }
        globalflags = be16_to_cpu(globalflags);
        TRACE("Global flags are %" PRIx32, globalflags);
        if (globalflags & NBD_FLAG_FIXED_NEWSTYLE) {
            fixedNewStyle = true;
            TRACE("Server supports fixed new style");
            clientflags |= NBD_FLAG_C_FIXED_NEWSTYLE;
        }
        if (globalflags & NBD_FLAG_NO_ZEROES) {
            zeroes = false;
            TRACE("Server supports no zeroes");
            clientflags |= NBD_FLAG_C_NO_ZEROES;
        }
        /* client requested flags */
        clientflags = cpu_to_be32(clientflags);
        if (write_sync(ioc, &clientflags, sizeof(clientflags)) !=
            sizeof(clientflags)) {
            error_setg(errp, "Failed to send clientflags field");
            goto fail;
        }
        if (tlscreds) {
            if (fixedNewStyle) {
                *outioc = nbd_receive_starttls(ioc, tlscreds, hostname, errp);
                if (!*outioc) {
                    goto fail;
                }
                ioc = *outioc;
            } else {
                error_setg(errp, "Server does not support STARTTLS");
                goto fail;
            }
        }
        if (!name) {
            TRACE("Using default NBD export name \"\"");
            name = "";
        }
        if (fixedNewStyle) {
            /* Check our desired export is present in the
             * server export list. Since NBD_OPT_EXPORT_NAME
             * cannot return an error message, running this
             * query gives us good error reporting if the
             * server required TLS
             */
            if (nbd_receive_query_exports(ioc, name, errp) < 0) {
                goto fail;
            }
        }
        /* write the export name request */
        if (nbd_send_option_request(ioc, NBD_OPT_EXPORT_NAME, -1, name,
                                    errp) < 0) {
            goto fail;
        }

        /* Read the response */
        if (read_sync(ioc, &s, sizeof(s)) != sizeof(s)) {
            error_setg(errp, "Failed to read export length");
            goto fail;
        }
        *size = be64_to_cpu(s);

        if (read_sync(ioc, flags, sizeof(*flags)) != sizeof(*flags)) {
            error_setg(errp, "Failed to read export flags");
            goto fail;
        }
        be16_to_cpus(flags);
    } else if (magic == NBD_CLIENT_MAGIC) {
        uint32_t oldflags;

        if (name) {
            error_setg(errp, "Server does not support export names");
            goto fail;
        }
        if (tlscreds) {
            error_setg(errp, "Server does not support STARTTLS");
            goto fail;
        }

        if (read_sync(ioc, &s, sizeof(s)) != sizeof(s)) {
            error_setg(errp, "Failed to read export length");
            goto fail;
        }
        *size = be64_to_cpu(s);
        TRACE("Size is %" PRIu64, *size);

        if (read_sync(ioc, &oldflags, sizeof(oldflags)) != sizeof(oldflags)) {
            error_setg(errp, "Failed to read export flags");
            goto fail;
        }
        be32_to_cpus(&oldflags);
        if (oldflags & ~0xffff) {
            error_setg(errp, "Unexpected export flags %0x" PRIx32, oldflags);
            goto fail;
        }
        *flags = oldflags;
    } else {
        error_setg(errp, "Bad magic received");
        goto fail;
    }

    TRACE("Size is %" PRIu64 ", export flags %" PRIx16, *size, *flags);
    if (zeroes && drop_sync(ioc, 124) != 124) {
        error_setg(errp, "Failed to read reserved block");
        goto fail;
    }
    rc = 0;

fail:
    return rc;
}


ssize_t nbd_receive_reply(QIOChannel *ioc, NBDReply *reply)
{
    uint8_t buf[NBD_REPLY_SIZE];
    uint32_t magic;
    ssize_t ret;

    ret = read_sync(ioc, buf, sizeof(buf));
    if (ret <= 0) {
        return ret;
    }

    if (ret != sizeof(buf)) {
        LOG("read failed");
        return -EINVAL;
    }

    /* Reply
       [ 0 ..  3]    magic   (NBD_REPLY_MAGIC)
       [ 4 ..  7]    error   (0 == no error)
       [ 7 .. 15]    handle
     */

    magic = ldl_be_p(buf);
    reply->error  = ldl_be_p(buf + 4);
    reply->handle = ldq_be_p(buf + 8);

    reply->error = nbd_errno_to_system_errno(reply->error);

    if (reply->error == ESHUTDOWN) {
        /* This works even on mingw which lacks a native ESHUTDOWN */
        LOG("server shutting down");
        return -EINVAL;
    }
    TRACE("Got reply: { magic = 0x%" PRIx32 ", .error = % " PRId32
          ", handle = %" PRIu64" }",
          magic, reply->error, reply->handle);

    if (magic != NBD_REPLY_MAGIC) {
        LOG("invalid magic (got 0x%" PRIx32 ")", magic);
        return -EINVAL;
    }
    return 0;
}


static ssize_t drop_sync(QIOChannel *ioc, size_t size)
{
    ssize_t ret = 0;
    char small[1024];
    char *buffer;

    buffer = sizeof(small) >= size ? small : g_malloc(MIN(65536, size));
    while (size > 0) {
        ssize_t count = read_sync(ioc, buffer, MIN(65536, size));

        if (count <= 0) {
            goto cleanup;
        }
        assert(count <= size);
        size -= count;
        ret += count;
    }

 cleanup:
    if (buffer != small) {
        g_free(buffer);
    }
    return ret;
}


static void nbd_send_opt_abort(QIOChannel *ioc)
{
    /* Technically, a compliant server is supposed to reply to us; but
     * older servers disconnected instead. At any rate, we're allowed
     * to disconnect without waiting for the server reply, so we don't
     * even care if the request makes it to the server, let alone
     * waiting around for whether the server replies. */
    nbd_send_option_request(ioc, NBD_OPT_ABORT, 0, NULL, NULL);
}


int nbd_client(int fd)
{
    int ret;
    int serrno;

    TRACE("Doing NBD loop");

    ret = ioctl(fd, NBD_DO_IT);
    if (ret < 0 && errno == EPIPE) {
        /* NBD_DO_IT normally returns EPIPE when someone has disconnected
         * the socket via NBD_DISCONNECT.  We do not want to return 1 in
         * that case.
         */
        ret = 0;
    }
    serrno = errno;

    TRACE("NBD loop returned %d: %s", ret, strerror(serrno));

    TRACE("Clearing NBD queue");
    ioctl(fd, NBD_CLEAR_QUE);

    TRACE("Clearing NBD socket");
    ioctl(fd, NBD_CLEAR_SOCK);

    errno = serrno;
    return ret;
}


static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply,
                                Error **errp)
{
    char *msg = NULL;
    int result = -1;

    if (!(reply->type & (1 << 31))) {
        return 1;
    }

    if (reply->length) {
        if (reply->length > NBD_MAX_BUFFER_SIZE) {
            error_setg(errp, "server's error message is too long");
            goto cleanup;
        }
        msg = g_malloc(reply->length + 1);
        if (read_sync(ioc, msg, reply->length) != reply->length) {
            error_setg(errp, "failed to read option error message");
            goto cleanup;
        }
        msg[reply->length] = '\0';
    }

    switch (reply->type) {
    case NBD_REP_ERR_UNSUP:
        TRACE("server doesn't understand request %" PRIx32
              ", attempting fallback", reply->option);
        result = 0;
        goto cleanup;

    case NBD_REP_ERR_POLICY:
        error_setg(errp, "Denied by server for option %" PRIx32,
                   reply->option);
        break;

    case NBD_REP_ERR_INVALID:
        error_setg(errp, "Invalid data length for option %" PRIx32,
                   reply->option);
        break;

    case NBD_REP_ERR_PLATFORM:
        error_setg(errp, "Server lacks support for option %" PRIx32,
                   reply->option);
        break;

    case NBD_REP_ERR_TLS_REQD:
        error_setg(errp, "TLS negotiation required before option %" PRIx32,
                   reply->option);
        break;

    case NBD_REP_ERR_SHUTDOWN:
        error_setg(errp, "Server shutting down before option %" PRIx32,
                   reply->option);
        break;

    default:
        error_setg(errp, "Unknown error code when asking for option %" PRIx32,
                   reply->option);
        break;
    }

    if (msg) {
        error_append_hint(errp, "%s\n", msg);
    }

 cleanup:
    g_free(msg);
    if (result < 0) {
        nbd_send_opt_abort(ioc);
    }
    return result;
}


ssize_t nbd_send_request(QIOChannel *ioc, NBDRequest *request)
{
    uint8_t buf[NBD_REQUEST_SIZE];
    ssize_t ret;

    TRACE("Sending request to server: "
          "{ .from = %" PRIu64", .len = %" PRIu32 ", .handle = %" PRIu64
          ", .flags = %" PRIx16 ", .type = %" PRIu16 " }",
          request->from, request->len, request->handle,
          request->flags, request->type);

    stl_be_p(buf, NBD_REQUEST_MAGIC);
    stw_be_p(buf + 4, request->flags);
    stw_be_p(buf + 6, request->type);
    stq_be_p(buf + 8, request->handle);
    stq_be_p(buf + 16, request->from);
    stl_be_p(buf + 24, request->len);

    ret = write_sync(ioc, buf, sizeof(buf));
    if (ret < 0) {
        return ret;
    }

    if (ret != sizeof(buf)) {
        LOG("writing to socket failed");
        return -EINVAL;
    }
    return 0;
}


static int nbd_receive_query_exports(QIOChannel *ioc,
                                     const char *wantname,
                                     Error **errp)
{
    bool foundExport = false;

    TRACE("Querying export list for '%s'", wantname);
    if (nbd_send_option_request(ioc, NBD_OPT_LIST, 0, NULL, errp) < 0) {
        return -1;
    }

    TRACE("Reading available export names");
    while (1) {
        int ret = nbd_receive_list(ioc, wantname, &foundExport, errp);

        if (ret < 0) {
            /* Server gave unexpected reply */
            return -1;
        } else if (ret == 0) {
            /* Done iterating. */
            if (!foundExport) {
                error_setg(errp, "No export with name '%s' available",
                           wantname);
                nbd_send_opt_abort(ioc);
                return -1;
            }
            TRACE("Found desired export name '%s'", wantname);
            return 0;
        }
    }
}


demux_mf: improve format string processing

Before this commit, the user could specify a printf format string
which wasn't verified, and could result in:
- Undefined behavior due to missing or non-matching arguments.
- Buffer overflow due to untested result length.

The offending code was added at commit 103a9609 (2002, mplayer svn):
git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@4566 b3059339-0415-0410-9bf9-f77b7e298cf2

It moved around but was not modified meaningfully until now.

Now we reject all conversion specifiers at the format except %%
and a simple subset of the valid specifiers. Also, we now use
snprintf to avoid buffer overflow.

The format string is provided by the user as part of mf:// URI.

Report and initial patch by Stefan Schiller.
Patch reviewed by @jeeb, @sfan5, Stefan Schiller.

static void demux_seek_mf(demuxer_t *demuxer, double seek_pts, int flags)
{
    mf_t *mf = demuxer->priv;
    double newpos = seek_pts * mf->sh->codec->fps;
    if (flags & SEEK_FACTOR)
        newpos = seek_pts * (mf->nr_of_files - 1);
    if (flags & SEEK_FORWARD) {
        newpos = ceil(newpos);
    } else {
        newpos = MPMIN(floor(newpos), mf->nr_of_files - 1);
    }
    mf->curr_frame = MPCLAMP((int)newpos, 0, mf->nr_of_files);
}

A format string vulnerability in mpv through 0.33.0 allows user-assisted remote attackers to achieve code execution via a crafted m3u playlist file.


static mf_t *open_mf_single(void *talloc_ctx, struct mp_log *log, char *filename)
{
    mf_t *mf = talloc_zero(talloc_ctx, mf_t);
    mf->log = log;
    mf_add(mf, filename);
    return mf;
}


static const char *probe_format(mf_t *mf, char *type, enum demux_check check)
{
    if (check > DEMUX_CHECK_REQUEST)
        return NULL;
    char *org_type = type;
    if (!type || !type[0]) {
        char *p = strrchr(mf->names[0], '.');
        if (p)
            type = p + 1;
    }
    for (int i = 0; type2format[i].type; i++) {
        if (type && strcasecmp(type, type2format[i].type) == 0)
            return type2format[i].codec;
    }
    if (check == DEMUX_CHECK_REQUEST) {
        if (!org_type) {
            MP_ERR(mf, "file type was not set! (try --mf-type=ext)\n");
        } else {
            MP_ERR(mf, "--mf-type set to an unknown codec!\n");
        }
    }
    return NULL;
}


static bool demux_mf_read_packet(struct demuxer *demuxer,
                                 struct demux_packet **pkt)
{
    mf_t *mf = demuxer->priv;
    if (mf->curr_frame >= mf->nr_of_files)
        return false;
    bool ok = false;

    struct stream *entry_stream = NULL;
    if (mf->streams)
        entry_stream = mf->streams[mf->curr_frame];
    struct stream *stream = entry_stream;
    if (!stream) {
        char *filename = mf->names[mf->curr_frame];
        if (filename) {
            stream = stream_create(filename, demuxer->stream_origin | STREAM_READ,
                                   demuxer->cancel, demuxer->global);
        }
    }

    if (stream) {
        stream_seek(stream, 0);
        bstr data = stream_read_complete(stream, NULL, MF_MAX_FILE_SIZE);
        if (data.len) {
            demux_packet_t *dp = new_demux_packet(data.len);
            if (dp) {
                memcpy(dp->buffer, data.start, data.len);
                dp->pts = mf->curr_frame / mf->sh->codec->fps;
                dp->keyframe = true;
                dp->stream = mf->sh->index;
                *pkt = dp;
                ok = true;
            }
        }
        talloc_free(data.start);
    }

    if (stream && stream != entry_stream)
        free_stream(stream);

    mf->curr_frame++;

    if (!ok)
        MP_ERR(demuxer, "error reading image file\n");

    return true;
}


static void mf_add(mf_t *mf, const char *fname)
{
    char *entry = talloc_strdup(mf, fname);
    MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry);
}


static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename)
{
    struct mp_log *log = d->log;
    int error_count = 0;
    int count = 0;

    mf_t *mf = talloc_zero(talloc_ctx, mf_t);
    mf->log = log;

    if (filename[0] == '@') {
        struct stream *s = stream_create(filename + 1,
                            d->stream_origin | STREAM_READ, d->cancel, d->global);
        if (s) {
            while (1) {
                char buf[512];
                int len = stream_read_peek(s, buf, sizeof(buf));
                if (!len)
                    break;
                bstr data = (bstr){buf, len};
                int pos = bstrchr(data, '\n');
                data = bstr_splice(data, 0, pos < 0 ? data.len : pos + 1);
                bstr fname = bstr_strip(data);
                if (fname.len) {
                    if (bstrchr(fname, '\0') >= 0) {
                        mp_err(log, "invalid filename\n");
                        break;
                    }
                    char *entry = bstrto0(mf, fname);
                    if (!mp_path_exists(entry)) {
                        mp_verbose(log, "file not found: '%s'\n", entry);
                    } else {
                        MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry);
                    }
                }
                stream_seek_skip(s, stream_tell(s) + data.len);
            }
            free_stream(s);

            mp_info(log, "number of files: %d\n", mf->nr_of_files);
            goto exit_mf;
        }
        mp_info(log, "%s is not indirect filelist\n", filename + 1);
    }

    if (strchr(filename, ',')) {
        mp_info(log, "filelist: %s\n", filename);
        bstr bfilename = bstr0(filename);

        while (bfilename.len) {
            bstr bfname;
            bstr_split_tok(bfilename, ",", &bfname, &bfilename);
            char *fname2 = bstrdup0(mf, bfname);

            if (!mp_path_exists(fname2))
                mp_verbose(log, "file not found: '%s'\n", fname2);
            else {
                mf_add(mf, fname2);
            }
            talloc_free(fname2);
        }
        mp_info(log, "number of files: %d\n", mf->nr_of_files);

        goto exit_mf;
    }

    size_t fname_avail = strlen(filename) + 32;
    char *fname = talloc_size(mf, fname_avail);

#if HAVE_GLOB
    if (!strchr(filename, '%')) {
        strcpy(fname, filename);
        if (!strchr(filename, '*'))
            strcat(fname, "*");

        mp_info(log, "search expr: %s\n", fname);

        glob_t gg;
        if (glob(fname, 0, NULL, &gg)) {
            talloc_free(mf);
            return NULL;
        }

        for (int i = 0; i < gg.gl_pathc; i++) {
            if (mp_path_isdir(gg.gl_pathv[i]))
                continue;
            mf_add(mf, gg.gl_pathv[i]);
        }
        mp_info(log, "number of files: %d\n", mf->nr_of_files);
        globfree(&gg);
        goto exit_mf;
    }
#endif

    // We're using arbitrary user input as printf format with 1 int argument.
    // Any format which uses exactly 1 int argument would be valid, but for
    // simplicity we reject all conversion specifiers except %% and simple
    // integer specifier: %[.][NUM]d where NUM is 1-3 digits (%.d is valid)
    const char *f = filename;
    int MAXDIGS = 3, nspec = 0, bad_spec = 0, c;

    while (nspec < 2 && (c = *f++)) {
        if (c != '%')
            continue;
        if (*f != '%') {
            nspec++;  // conversion specifier which isn't %%
            if (*f == '.')
                f++;
            for (int ndig = 0; mp_isdigit(*f) && ndig < MAXDIGS; ndig++, f++)
                /* no-op */;
            if (*f != 'd') {
                bad_spec++;  // not int, or beyond our validation capacity
                break;
            }
        }
        // *f is '%' or 'd'
        f++;
    }

    // nspec==0 (zero specifiers) is rejected because fname wouldn't advance.
    if (bad_spec || nspec != 1) {
        mp_err(log, "unsupported expr format: '%s'\n", filename);
        goto exit_mf;
    }

    mp_info(log, "search expr: %s\n", filename);

    while (error_count < 5) {
        if (snprintf(fname, fname_avail, filename, count++) >= fname_avail) {
            mp_err(log, "format result too long: '%s'\n", filename);
            goto exit_mf;
        }
        if (!mp_path_exists(fname)) {
            error_count++;
            mp_verbose(log, "file not found: '%s'\n", fname);
        } else {
            mf_add(mf, fname);
        }
    }

    mp_info(log, "number of files: %d\n", mf->nr_of_files);

exit_mf:
    return mf;
}


static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
{
    mf_t *mf;

    if (strncmp(demuxer->stream->url, "mf://", 5) == 0 &&
        demuxer->stream->info && strcmp(demuxer->stream->info->name, "mf") == 0)
    {
        mf = open_mf_pattern(demuxer, demuxer, demuxer->stream->url + 5);
    } else {
        mf = open_mf_single(demuxer, demuxer->log, demuxer->stream->url);
        int bog = 0;
        MP_TARRAY_APPEND(mf, mf->streams, bog, demuxer->stream);
    }

    if (!mf || mf->nr_of_files < 1)
        goto error;

    double mf_fps;
    char *mf_type;
    mp_read_option_raw(demuxer->global, "mf-fps", &m_option_type_double, &mf_fps);
    mp_read_option_raw(demuxer->global, "mf-type", &m_option_type_string, &mf_type);

    const char *codec = mp_map_mimetype_to_video_codec(demuxer->stream->mime_type);
    if (!codec || (mf_type && mf_type[0]))
        codec = probe_format(mf, mf_type, check);
    talloc_free(mf_type);
    if (!codec)
        goto error;

    mf->curr_frame = 0;

    // create a new video stream header
    struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
    struct mp_codec_params *c = sh->codec;

    c->codec = codec;
    c->disp_w = 0;
    c->disp_h = 0;
    c->fps = mf_fps;
    c->reliable_fps = true;

    demux_add_sh_stream(demuxer, sh);

    mf->sh = sh;
    demuxer->priv = (void *)mf;
    demuxer->seekable = true;
    demuxer->duration = mf->nr_of_files / mf->sh->codec->fps;

    return 0;

error:
    return -1;
}


static void demux_close_mf(demuxer_t *demuxer)
{
}


Handle set where internalSetter and Proxy are both true

Summary:
If putComputed is called on a proxy whose target's prototype
is an array with a propname of 'length', then internalSetter will be
true, and the receiver will be a proxy.  In that case, proxy needs to
win; the behavior may assert or be UB otherwise.

Reviewed By: tmikov

Differential Revision: D23916279

fbshipit-source-id: c760356d48a02ece565fb4bc1acdafd7ccad7c68

CallResult<bool> JSObject::putComputedWithReceiver_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    Handle<> valueHandle,
    Handle<> receiver,
    PropOpFlags opFlags) {
  assert(
      !opFlags.getMustExist() &&
      "mustExist flag cannot be used with computed properties");

  // Try the fast-path first: has "index-like" properties, the "name"
  // already is a valid integer index, selfHandle and receiver are the
  // same, and it is present in storage.
  if (selfHandle->flags_.fastIndexProperties) {
    if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
      if (selfHandle.getHermesValue().getRaw() == receiver->getRaw()) {
        if (haveOwnIndexed(selfHandle.get(), runtime, *arrayIndex)) {
          auto result =
              setOwnIndexed(selfHandle, runtime, *arrayIndex, valueHandle);
          if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
            return ExecutionStatus::EXCEPTION;
          if (LLVM_LIKELY(*result))
            return true;
          if (opFlags.getThrowOnError()) {
            // TODO: better message.
            return runtime->raiseTypeError(
                "Cannot assign to read-only property");
          }
          return false;
        }
      }
    }
  }

  // If nameValHandle is an object, we should convert it to string now,
  // because toString may have side-effect, and we want to do this only
  // once.
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  auto nameValPrimitiveHandle = *converted;

  ComputedPropertyDescriptor desc;

  // Look for the property in this object or along the prototype chain.
  MutableHandle<JSObject> propObj{runtime};
  if (LLVM_UNLIKELY(
          getComputedPrimitiveDescriptor(
              selfHandle, runtime, nameValPrimitiveHandle, propObj, desc) ==
          ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }

  // If the property exists (or, we hit a proxy/hostobject on the way
  // up the chain)
  if (propObj) {
    // Get the simple case out of the way: If the property already
    // exists on selfHandle, is not an accessor, selfHandle and
    // receiver are the same, selfHandle is not a host
    // object/proxy/internal setter, and the property is writable,
    // just write into the same slot.

    if (LLVM_LIKELY(
            selfHandle == propObj &&
            selfHandle.getHermesValue().getRaw() == receiver->getRaw() &&
            !desc.flags.accessor && !desc.flags.internalSetter &&
            !desc.flags.hostObject && !desc.flags.proxyObject &&
            desc.flags.writable)) {
      if (LLVM_UNLIKELY(
              setComputedSlotValue(selfHandle, runtime, desc, valueHandle) ==
              ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      return true;
    }

    // Is it an accessor?
    if (LLVM_UNLIKELY(desc.flags.accessor)) {
      auto *accessor = vmcast<PropertyAccessor>(
          getComputedSlotValue(propObj.get(), runtime, desc));

      // If it is a read-only accessor, fail.
      if (!accessor->setter) {
        if (opFlags.getThrowOnError()) {
          return runtime->raiseTypeErrorForValue(
              "Cannot assign to property ",
              nameValPrimitiveHandle,
              " which has only a getter");
        }
        return false;
      }

      // Execute the accessor on this object.
      if (accessor->setter.get(runtime)->executeCall1(
              runtime->makeHandle(accessor->setter),
              runtime,
              receiver,
              valueHandle.get()) == ExecutionStatus::EXCEPTION) {
        return ExecutionStatus::EXCEPTION;
      }
      return true;
    }

    if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
      assert(
          !opFlags.getMustExist() &&
          "MustExist cannot be used with Proxy objects");
      CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
      if (key == ExecutionStatus::EXCEPTION)
        return ExecutionStatus::EXCEPTION;
      CallResult<bool> setRes =
          JSProxy::setComputed(propObj, runtime, *key, valueHandle, receiver);
      if (LLVM_UNLIKELY(setRes == ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      if (!*setRes && opFlags.getThrowOnError()) {
        // TODO: better message.
        return runtime->raiseTypeError(
            TwineChar16("Proxy trap returned false for property"));
      }
      return setRes;
    }

    if (LLVM_UNLIKELY(!desc.flags.writable)) {
      if (desc.flags.staticBuiltin) {
        SymbolID id{};
        LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
        return raiseErrorForOverridingStaticBuiltin(
            selfHandle, runtime, runtime->makeHandle(id));
      }
      if (opFlags.getThrowOnError()) {
        return runtime->raiseTypeErrorForValue(
            "Cannot assign to read-only property ", nameValPrimitiveHandle, "");
      }
      return false;
    }

    if (selfHandle == propObj && desc.flags.internalSetter) {
      SymbolID id{};
      LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
      return internalSetter(
          selfHandle,
          runtime,
          id,
          desc.castToNamedPropertyDescriptorRef(),
          valueHandle,
          opFlags);
    }
  }

  // The property does not exist as an conventional own property on
  // this object.

  MutableHandle<JSObject> receiverHandle{runtime, *selfHandle};
  if (selfHandle.getHermesValue().getRaw() != receiver->getRaw() ||
      receiverHandle->isHostObject() || receiverHandle->isProxyObject()) {
    if (selfHandle.getHermesValue().getRaw() != receiver->getRaw()) {
      receiverHandle = dyn_vmcast<JSObject>(*receiver);
    }
    if (!receiverHandle) {
      return false;
    }
    CallResult<bool> descDefinedRes = getOwnComputedPrimitiveDescriptor(
        receiverHandle, runtime, nameValPrimitiveHandle, IgnoreProxy::No, desc);
    if (LLVM_UNLIKELY(descDefinedRes == ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    DefinePropertyFlags dpf;
    if (*descDefinedRes) {
      if (LLVM_UNLIKELY(desc.flags.accessor || !desc.flags.writable)) {
        return false;
      }

      if (LLVM_LIKELY(
              !desc.flags.internalSetter && !receiverHandle->isHostObject() &&
              !receiverHandle->isProxyObject())) {
        if (LLVM_UNLIKELY(
                setComputedSlotValue(
                    receiverHandle, runtime, desc, valueHandle) ==
                ExecutionStatus::EXCEPTION)) {
          return ExecutionStatus::EXCEPTION;
        }
        return true;
      }
    }

    if (LLVM_UNLIKELY(
            desc.flags.internalSetter || receiverHandle->isHostObject() ||
            receiverHandle->isProxyObject())) {
      // If putComputed is called on a proxy whose target's prototype
      // is an array with a propname of 'length', then internalSetter
      // will be true, and the receiver will be a proxy.  In that case,
      // proxy wins.
      if (receiverHandle->isProxyObject()) {
        if (*descDefinedRes) {
          dpf.setValue = 1;
        } else {
          dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
        }
        return JSProxy::defineOwnProperty(
            receiverHandle,
            runtime,
            nameValPrimitiveHandle,
            dpf,
            valueHandle,
            opFlags);
      }
      SymbolID id{};
      LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
      if (desc.flags.internalSetter) {
        return internalSetter(
            receiverHandle,
            runtime,
            id,
            desc.castToNamedPropertyDescriptorRef(),
            valueHandle,
            opFlags);
      }
      assert(
          receiverHandle->isHostObject() && "descriptor flags are impossible");
      return vmcast<HostObject>(receiverHandle.get())->set(id, *valueHandle);
    }
  }

  /// Can we add more properties?
  if (LLVM_UNLIKELY(!receiverHandle->isExtensible())) {
    if (opFlags.getThrowOnError()) {
      return runtime->raiseTypeError(
          "cannot add a new property"); // TODO: better message.
    }
    return false;
  }

  // If we have indexed storage we must check whether the property is an index,
  // and if it is, store it in indexed storage.
  if (receiverHandle->flags_.indexedStorage) {
    OptValue<uint32_t> arrayIndex;
    MutableHandle<StringPrimitive> strPrim{runtime};
    TO_ARRAY_INDEX(runtime, nameValPrimitiveHandle, strPrim, arrayIndex);
    if (arrayIndex) {
      // Check whether we need to update array's ".length" property.
      if (auto *array = dyn_vmcast<JSArray>(receiverHandle.get())) {
        if (LLVM_UNLIKELY(*arrayIndex >= JSArray::getLength(array))) {
          auto cr = putNamed_RJS(
              receiverHandle,
              runtime,
              Predefined::getSymbolID(Predefined::length),
              runtime->makeHandle(
                  HermesValue::encodeNumberValue(*arrayIndex + 1)),
              opFlags);
          if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION))
            return ExecutionStatus::EXCEPTION;
          if (LLVM_UNLIKELY(!*cr))
            return false;
        }
      }

      auto result =
          setOwnIndexed(receiverHandle, runtime, *arrayIndex, valueHandle);
      if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
        return ExecutionStatus::EXCEPTION;
      if (LLVM_LIKELY(*result))
        return true;

      if (opFlags.getThrowOnError()) {
        // TODO: better message.
        return runtime->raiseTypeError("Cannot assign to read-only property");
      }
      return false;
    }
  }

  SymbolID id{};
  LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);

  // Add a new named property.
  return addOwnProperty(
      receiverHandle,
      runtime,
      id,
      DefinePropertyFlags::getDefaultNewPropertyFlags(),
      valueHandle,
      opFlags);
}

An out-of-bounds read in the JavaScript Interpreter in Facebook Hermes prior to commit 8cb935cd3b2321c46aa6b7ed8454d95c75a7fca0 allows attackers to cause a denial of service attack or possible further memory corruption via crafted JavaScript. Note that this is only exploitable if the application using Hermes permits evaluation of untrusted JavaScript. Hence, most React Native applications are not affected.


CallResult<bool> JSObject::putNamedWithReceiver_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    Handle<> valueHandle,
    Handle<> receiver,
    PropOpFlags opFlags) {
  NamedPropertyDescriptor desc;

  // Look for the property in this object or along the prototype chain.
  JSObject *propObj = getNamedDescriptor(
      selfHandle,
      runtime,
      name,
      PropertyFlags::defaultNewNamedPropertyFlags(),
      desc);

  // If the property exists (or, we hit a proxy/hostobject on the way
  // up the chain)
  if (propObj) {
    // Get the simple case out of the way: If the property already
    // exists on selfHandle, is not an accessor, selfHandle and
    // receiver are the same, selfHandle is not a host
    // object/proxy/internal setter, and the property is writable,
    // just write into the same slot.

    if (LLVM_LIKELY(
            *selfHandle == propObj &&
            selfHandle.getHermesValue().getRaw() == receiver->getRaw() &&
            !desc.flags.accessor && !desc.flags.internalSetter &&
            !desc.flags.hostObject && !desc.flags.proxyObject &&
            desc.flags.writable)) {
      setNamedSlotValue(
          *selfHandle, runtime, desc, valueHandle.getHermesValue());
      return true;
    }

    if (LLVM_UNLIKELY(desc.flags.accessor)) {
      auto *accessor =
          vmcast<PropertyAccessor>(getNamedSlotValue(propObj, runtime, desc));

      // If it is a read-only accessor, fail.
      if (!accessor->setter) {
        if (opFlags.getThrowOnError()) {
          return runtime->raiseTypeError(
              TwineChar16("Cannot assign to property '") +
              runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
              "' which has only a getter");
        }
        return false;
      }

      // Execute the accessor on this object.
      if (accessor->setter.get(runtime)->executeCall1(
              runtime->makeHandle(accessor->setter),
              runtime,
              receiver,
              *valueHandle) == ExecutionStatus::EXCEPTION) {
        return ExecutionStatus::EXCEPTION;
      }
      return true;
    }

    if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
      assert(
          !opFlags.getMustExist() &&
          "MustExist cannot be used with Proxy objects");
      CallResult<bool> setRes = JSProxy::setNamed(
          runtime->makeHandle(propObj), runtime, name, valueHandle, receiver);
      if (LLVM_UNLIKELY(setRes == ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      if (!*setRes && opFlags.getThrowOnError()) {
        return runtime->raiseTypeError(
            TwineChar16("Proxy set returned false for property '") +
            runtime->getIdentifierTable().getStringView(runtime, name) + "'");
      }
      return setRes;
    }

    if (LLVM_UNLIKELY(!desc.flags.writable)) {
      if (desc.flags.staticBuiltin) {
        return raiseErrorForOverridingStaticBuiltin(
            selfHandle, runtime, runtime->makeHandle(name));
      }
      if (opFlags.getThrowOnError()) {
        return runtime->raiseTypeError(
            TwineChar16("Cannot assign to read-only property '") +
            runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
            "'");
      }
      return false;
    }

    if (*selfHandle == propObj && desc.flags.internalSetter) {
      return internalSetter(
          selfHandle, runtime, name, desc, valueHandle, opFlags);
    }
  }

  // The property does not exist as an conventional own property on
  // this object.

  MutableHandle<JSObject> receiverHandle{runtime, *selfHandle};
  if (selfHandle.getHermesValue().getRaw() != receiver->getRaw() ||
      receiverHandle->isHostObject() || receiverHandle->isProxyObject()) {
    if (selfHandle.getHermesValue().getRaw() != receiver->getRaw()) {
      receiverHandle = dyn_vmcast<JSObject>(*receiver);
    }
    if (!receiverHandle) {
      return false;
    }

    if (getOwnNamedDescriptor(receiverHandle, runtime, name, desc)) {
      if (LLVM_UNLIKELY(desc.flags.accessor || !desc.flags.writable)) {
        return false;
      }

      assert(
          !receiverHandle->isHostObject() && !receiverHandle->isProxyObject() &&
          "getOwnNamedDescriptor never sets hostObject or proxyObject flags");

      setNamedSlotValue(
          *receiverHandle, runtime, desc, valueHandle.getHermesValue());
      return true;
    }

    // Now deal with host and proxy object cases.  We need to call
    // getOwnComputedPrimitiveDescriptor because it knows how to call
    // the [[getOwnProperty]] Proxy impl if needed.
    if (LLVM_UNLIKELY(
            receiverHandle->isHostObject() ||
            receiverHandle->isProxyObject())) {
      if (receiverHandle->isHostObject()) {
        return vmcast<HostObject>(receiverHandle.get())
            ->set(name, *valueHandle);
      }
      ComputedPropertyDescriptor desc;
      Handle<> nameValHandle = runtime->makeHandle(name);
      CallResult<bool> descDefinedRes = getOwnComputedPrimitiveDescriptor(
          receiverHandle, runtime, nameValHandle, IgnoreProxy::No, desc);
      if (LLVM_UNLIKELY(descDefinedRes == ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      DefinePropertyFlags dpf;
      if (*descDefinedRes) {
        dpf.setValue = 1;
      } else {
        dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
      }
      return JSProxy::defineOwnProperty(
          receiverHandle, runtime, nameValHandle, dpf, valueHandle, opFlags);
    }
  }

  // Does the caller require it to exist?
  if (LLVM_UNLIKELY(opFlags.getMustExist())) {
    return runtime->raiseReferenceError(
        TwineChar16("Property '") +
        runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
        "' doesn't exist");
  }

  // Add a new property.

  return addOwnProperty(
      receiverHandle,
      runtime,
      name,
      DefinePropertyFlags::getDefaultNewPropertyFlags(),
      valueHandle,
      opFlags);
}


CallResult<bool> JSObject::defineOwnComputedPrimitive(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    DefinePropertyFlags dpFlags,
    Handle<> valueOrAccessor,
    PropOpFlags opFlags) {
  assert(
      !nameValHandle->isObject() &&
      "nameValHandle passed to "
      "defineOwnComputedPrimitive() cannot be "
      "an object");
  assert(
      !opFlags.getMustExist() && "cannot use mustExist with defineOwnProperty");
  assert(
      !(dpFlags.setValue && dpFlags.isAccessor()) &&
      "Cannot set both value and accessor");
  assert(
      (dpFlags.setValue || dpFlags.isAccessor() ||
       valueOrAccessor.get().isUndefined()) &&
      "value must be undefined when all of setValue/setSetter/setGetter are "
      "false");
  assert(
      !dpFlags.enableInternalSetter &&
      "Cannot set internalSetter on a computed property");
#ifndef NDEBUG
  if (dpFlags.isAccessor()) {
    assert(valueOrAccessor.get().isPointer() && "accessor must be non-empty");
    assert(
        !dpFlags.setWritable && !dpFlags.writable &&
        "writable must not be set with accessors");
  }
#endif

  // If the name is a valid integer array index, store it here.
  OptValue<uint32_t> arrayIndex;

  // If we have indexed storage, we must attempt to convert the name to array
  // index, even if the conversion is expensive.
  if (selfHandle->flags_.indexedStorage) {
    MutableHandle<StringPrimitive> strPrim{runtime};
    TO_ARRAY_INDEX(runtime, nameValHandle, strPrim, arrayIndex);
  }

  SymbolID id{};

  // If not storing a property with an array index name, or if we don't have
  // indexed storage, just pass to the named routine.
  if (!arrayIndex) {
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
    return defineOwnPropertyInternal(
        selfHandle, runtime, id, dpFlags, valueOrAccessor, opFlags);
  }

  // At this point we know that we have indexed storage and that the property
  // has an index-like name.

  // First check if a named property with the same name exists.
  if (selfHandle->clazz_.get(runtime)->getHasIndexLikeProperties()) {
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);

    NamedPropertyDescriptor desc;
    auto pos = findProperty(selfHandle, runtime, id, desc);
    // If we found a named property, update it.
    if (pos) {
      return updateOwnProperty(
          selfHandle,
          runtime,
          id,
          *pos,
          desc,
          dpFlags,
          valueOrAccessor,
          opFlags);
    }
  }

  // Does an indexed property with that index exist?
  auto indexedPropPresent =
      getOwnIndexedPropertyFlags(selfHandle.get(), runtime, *arrayIndex);
  if (indexedPropPresent) {
    // The current value of the property.
    HermesValue curValueOrAccessor =
        getOwnIndexed(selfHandle.get(), runtime, *arrayIndex);

    auto updateStatus = checkPropertyUpdate(
        runtime,
        *indexedPropPresent,
        dpFlags,
        curValueOrAccessor,
        valueOrAccessor,
        opFlags);
    if (updateStatus == ExecutionStatus::EXCEPTION)
      return ExecutionStatus::EXCEPTION;
    if (updateStatus->first == PropertyUpdateStatus::failed)
      return false;

    // The property update is valid, but can the property remain an "indexed"
    // property, or do we need to convert it to a named property?
    // If the property flags didn't change, the property remains indexed.
    if (updateStatus->second == *indexedPropPresent) {
      // If the value doesn't change, we are done.
      if (updateStatus->first == PropertyUpdateStatus::done)
        return true;

      // If we successfully updated the value, we are done.
      auto result =
          setOwnIndexed(selfHandle, runtime, *arrayIndex, valueOrAccessor);
      if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
        return ExecutionStatus::EXCEPTION;
      if (*result)
        return true;

      if (opFlags.getThrowOnError()) {
        // TODO: better error message.
        return runtime->raiseTypeError(
            "cannot change read-only property value");
      }

      return false;
    }

    // OK, we need to convert an indexed property to a named one.

    // Check whether to use the supplied value, or to reuse the old one, as we
    // are simply reconfiguring it.
    MutableHandle<> value{runtime};
    if (dpFlags.setValue || dpFlags.isAccessor()) {
      value = valueOrAccessor.get();
    } else {
      value = curValueOrAccessor;
    }

    // Update dpFlags to match the existing property flags.
    dpFlags.setEnumerable = 1;
    dpFlags.setWritable = 1;
    dpFlags.setConfigurable = 1;
    dpFlags.enumerable = updateStatus->second.enumerable;
    dpFlags.writable = updateStatus->second.writable;
    dpFlags.configurable = updateStatus->second.configurable;

    // Delete the existing indexed property.
    if (!deleteOwnIndexed(selfHandle, runtime, *arrayIndex)) {
      if (opFlags.getThrowOnError()) {
        // TODO: better error message.
        return runtime->raiseTypeError("Cannot define property");
      }
      return false;
    }

    // Add the new named property.
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
    return addOwnProperty(selfHandle, runtime, id, dpFlags, value, opFlags);
  }

  /// Can we add new properties?
  if (!selfHandle->isExtensible()) {
    if (opFlags.getThrowOnError()) {
      return runtime->raiseTypeError(
          "cannot add a new property"); // TODO: better message.
    }
    return false;
  }

  // This is a new property with an index-like name.
  // Check whether we need to update array's ".length" property.
  bool updateLength = false;
  if (auto arrayHandle = Handle<JSArray>::dyn_vmcast(selfHandle)) {
    if (LLVM_UNLIKELY(*arrayIndex >= JSArray::getLength(*arrayHandle))) {
      NamedPropertyDescriptor lengthDesc;
      bool lengthPresent = getOwnNamedDescriptor(
          arrayHandle,
          runtime,
          Predefined::getSymbolID(Predefined::length),
          lengthDesc);
      (void)lengthPresent;
      assert(lengthPresent && ".length must be present in JSArray");

      if (!lengthDesc.flags.writable) {
        if (opFlags.getThrowOnError()) {
          return runtime->raiseTypeError(
              "Cannot assign to read-only 'length' property of array");
        }
        return false;
      }

      updateLength = true;
    }
  }

  bool newIsIndexed = canNewPropertyBeIndexed(dpFlags);
  if (newIsIndexed) {
    auto result = setOwnIndexed(
        selfHandle,
        runtime,
        *arrayIndex,
        dpFlags.setValue ? valueOrAccessor : Runtime::getUndefinedValue());
    if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
      return ExecutionStatus::EXCEPTION;
    if (!*result) {
      if (opFlags.getThrowOnError()) {
        // TODO: better error message.
        return runtime->raiseTypeError("Cannot define property");
      }
      return false;
    }
  }

  // If this is an array and we need to update ".length", do so.
  if (updateLength) {
    // This should always succeed since we are simply enlarging the length.
    auto res = JSArray::setLength(
        Handle<JSArray>::vmcast(selfHandle), runtime, *arrayIndex + 1, opFlags);
    (void)res;
    assert(
        res != ExecutionStatus::EXCEPTION && *res &&
        "JSArray::setLength() failed unexpectedly");
  }

  if (newIsIndexed)
    return true;

  // We are adding a new property with an index-like name.
  LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
  return addOwnProperty(
      selfHandle, runtime, id, dpFlags, valueOrAccessor, opFlags);
}


CallResult<bool> JSObject::defineOwnPropertyInternal(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    DefinePropertyFlags dpFlags,
    Handle<> valueOrAccessor,
    PropOpFlags opFlags) {
  assert(
      !opFlags.getMustExist() && "cannot use mustExist with defineOwnProperty");
  assert(
      !(dpFlags.setValue && dpFlags.isAccessor()) &&
      "Cannot set both value and accessor");
  assert(
      (dpFlags.setValue || dpFlags.isAccessor() ||
       valueOrAccessor.get().isUndefined()) &&
      "value must be undefined when all of setValue/setSetter/setGetter are "
      "false");
#ifndef NDEBUG
  if (dpFlags.isAccessor()) {
    assert(valueOrAccessor.get().isPointer() && "accessor must be non-empty");
    assert(
        !dpFlags.setWritable && !dpFlags.writable &&
        "writable must not be set with accessors");
  }
#endif

  // Is it an existing property.
  NamedPropertyDescriptor desc;
  auto pos = findProperty(selfHandle, runtime, name, desc);
  if (pos) {
    return updateOwnProperty(
        selfHandle,
        runtime,
        name,
        *pos,
        desc,
        dpFlags,
        valueOrAccessor,
        opFlags);
  }

  if (LLVM_UNLIKELY(
          selfHandle->flags_.lazyObject || selfHandle->flags_.proxyObject)) {
    if (selfHandle->flags_.proxyObject) {
      return JSProxy::defineOwnProperty(
          selfHandle,
          runtime,
          name.isUniqued() ? runtime->makeHandle(HermesValue::encodeStringValue(
                                 runtime->getStringPrimFromSymbolID(name)))
                           : runtime->makeHandle(name),
          dpFlags,
          valueOrAccessor,
          opFlags);
    }
    assert(selfHandle->flags_.lazyObject && "descriptor flags are impossible");
    // if the property was not found and the object is lazy we need to
    // initialize it and try again.
    JSObject::initializeLazyObject(runtime, selfHandle);
    return defineOwnPropertyInternal(
        selfHandle, runtime, name, dpFlags, valueOrAccessor, opFlags);
  }

  return addOwnProperty(
      selfHandle, runtime, name, dpFlags, valueOrAccessor, opFlags);
}


Fix a check fail

PiperOrigin-RevId: 372011072
Change-Id: I1062cfaed0aa16884e9a16312483794d188db76f

  explicit LoadAndRemapMatrixOp(OpKernelConstruction* context)
      : OpKernel(context) {
    OP_REQUIRES_OK(context, context->GetAttr("num_rows", &num_rows_));
    OP_REQUIRES_OK(context, context->GetAttr("num_cols", &num_cols_));
    OP_REQUIRES_OK(
        context, context->GetAttr("max_rows_in_memory", &max_rows_in_memory_));
  }

TensorFlow is an end-to-end open source platform for machine learning. An attacker can cause a denial of service by exploiting a `CHECK`-failure coming from `tf.raw_ops.LoadAndRemapMatrix`. This is because the implementation(https://github.com/tensorflow/tensorflow/blob/d94227d43aa125ad8b54115c03cece54f6a1977b/tensorflow/core/kernels/ragged_tensor_to_tensor_op.cc#L219-L222) assumes that the `ckpt_path` is always a valid scalar. However, an attacker can send any other tensor as the first argument of `LoadAndRemapMatrix`. This would cause the rank `CHECK` in `scalar<T>()()` to trigger and terminate the process. 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.


Status RemapVectorToMap(const TTypes<const int64>::Vec& remapping,
                        std::vector<bool>* id_present,
                        std::unordered_map<int64, int64>* old_id_to_new_id) {
  id_present->clear();
  id_present->resize(remapping.size(), false);
  for (int i = 0; i < remapping.size(); ++i) {
    const int64 old_id = remapping(i);
    if (old_id < 0) continue;
    (*id_present)[i] = true;
    if (!gtl::InsertIfNotPresent(old_id_to_new_id, old_id, i)) {
      return errors::Unimplemented(
          strings::StrCat("Old ID ", old_id, " is mapped to both new ID ",
                          old_id_to_new_id->at(old_id), " and ", i,
                          ", which is not supported."));
    }
  }
  return Status::OK();
}