xref: /freebsd/contrib/pkgconf/libpkgconf/bytecode.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
1 /*
2  * bytecode.c
3  * variable expansion bytecode evaluator
4  *
5  * SPDX-License-Identifier: pkgconf
6  *
7  * Copyright (c) 2026 pkgconf authors (see AUTHORS).
8  *
9  * Permission to use, copy, modify, and/or distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * This software is provided 'as is' and without any warranty, express or
14  * implied.  In no event shall the authors be liable for any damages arising
15  * from the use of this software.
16  */
17 
18 #include <libpkgconf/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20 
21 /*
22  * !doc
23  *
24  * libpkgconf `bytecode` module
25  * ============================
26  *
27  * The libpkgconf `bytecode` module contains the functions related to
28  * evaluating variable expansion bytecode.
29  */
30 
31 #define PKGCONF_EVAL_MAX_OUTPUT (PKGCONF_BUFSIZE - 1)
32 #define PKGCONF_EVAL_MAX_ITERATIONS (512)
33 
34 static bool
pkgconf_bytecode_eval_append_slice(pkgconf_bytecode_eval_ctx_t * ctx,pkgconf_buffer_t * out,const char * p,size_t n)35 pkgconf_bytecode_eval_append_slice(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *p, size_t n)
36 {
37 	size_t cur = pkgconf_buffer_len(out);
38 	if (cur >= PKGCONF_EVAL_MAX_OUTPUT)
39 		return false;
40 
41 	if (n > PKGCONF_EVAL_MAX_OUTPUT - cur)
42 	{
43 		pkgconf_warn(ctx->client, "warning: truncating very long variable to 64KB\n");
44 
45 		n = PKGCONF_EVAL_MAX_OUTPUT - cur;
46 		if (!pkgconf_buffer_append_slice(out, p, n))
47 			pkgconf_error(ctx->client, "pkgconf_bytecode_eval_append_slice: failed to append to slice");
48 
49 		return false;
50 	}
51 
52 	return pkgconf_buffer_append_slice(out, p, n);
53 }
54 
55 static bool
pkgconf_bytecode_eval_append(pkgconf_bytecode_eval_ctx_t * ctx,pkgconf_buffer_t * out,const char * s)56 pkgconf_bytecode_eval_append(pkgconf_bytecode_eval_ctx_t *ctx, pkgconf_buffer_t *out, const char *s)
57 {
58 	if (s == NULL || *s == '\0')
59 		return true;
60 
61 	return pkgconf_bytecode_eval_append_slice(ctx, out, s, strlen(s));
62 }
63 
64 static bool
65 pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot);
66 
67 static pkgconf_variable_t *
pkgconf_bytecode_eval_scan(const pkgconf_list_t * vars,const char * name,size_t nlen,unsigned int require_flags,unsigned int forbid_flags)68 pkgconf_bytecode_eval_scan(const pkgconf_list_t *vars, const char *name, size_t nlen, unsigned int require_flags, unsigned int forbid_flags)
69 {
70 	const pkgconf_node_t *node;
71 
72 	PKGCONF_FOREACH_LIST_ENTRY(vars->head, node)
73 	{
74 		pkgconf_variable_t *v = node->data;
75 
76 		if ((v->flags & require_flags) != require_flags)
77 			continue;
78 
79 		if ((v->flags & forbid_flags) != 0)
80 			continue;
81 
82 		if (pkgconf_str_eq_slice(v->key, name, nlen))
83 			return v;
84 	}
85 
86 	return NULL;
87 }
88 
89 pkgconf_variable_t *
pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t * ctx,const char * name,size_t nlen)90 pkgconf_bytecode_eval_lookup_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen)
91 {
92 	pkgconf_variable_t *v;
93 
94 	if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, PKGCONF_VARIABLEF_OVERRIDE, 0)) != NULL)
95 		return v;
96 
97 	if (ctx->vars != NULL && (v = pkgconf_bytecode_eval_scan(ctx->vars, name, nlen, 0, 0)) != NULL)
98 		return v;
99 
100 	if ((v = pkgconf_bytecode_eval_scan(&ctx->client->global_vars, name, nlen, 0, PKGCONF_VARIABLEF_OVERRIDE)) != NULL)
101 		return v;
102 
103 	return NULL;
104 }
105 
106 static bool
pkgconf_bytecode_eval_var(pkgconf_bytecode_eval_ctx_t * ctx,const char * name,size_t nlen,pkgconf_buffer_t * out,bool * saw_sysroot)107 pkgconf_bytecode_eval_var(pkgconf_bytecode_eval_ctx_t *ctx, const char *name, size_t nlen, pkgconf_buffer_t *out, bool *saw_sysroot)
108 {
109 	pkgconf_variable_t *v;
110 
111 	v = pkgconf_bytecode_eval_lookup_var(ctx, name, nlen);
112 	if (v == NULL)
113 		return true;
114 
115 	if (v->expanding)
116 		return false;
117 
118 	v->expanding = true;
119 
120 	bool inner_saw = false;
121 	bool ok = pkgconf_bytecode_eval_internal(ctx, &v->bc, out, &inner_saw);
122 
123 	v->expanding = false;
124 
125 	if (!ok)
126 		return false;
127 
128 	if (saw_sysroot != NULL)
129 		*saw_sysroot |= inner_saw;
130 
131 	return true;
132 }
133 
134 static bool
pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t * ctx,const pkgconf_bytecode_t * bc,pkgconf_buffer_t * out,bool * saw_sysroot)135 pkgconf_bytecode_eval_internal(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot)
136 {
137 	(void) ctx;
138 
139 	if (bc == NULL || out == NULL)
140 		return false;
141 
142 	const uint8_t *p = bc->base;
143 	const uint8_t *end = bc->base + bc->len;
144 
145 	if (++ctx->expansions > PKGCONF_EVAL_MAX_ITERATIONS)
146 	{
147 		pkgconf_warn(ctx->client,
148 			"warning: bytecode program exceeds iteration limit (" SIZE_FMT_SPECIFIER ")\n",
149 			ctx->expansions - 1);
150 		return false;
151 	}
152 
153 	while (p < end)
154 	{
155 		const pkgconf_bytecode_op_t *op =
156 			(const pkgconf_bytecode_op_t *)p;
157 
158 		if ((const uint8_t *)op + sizeof(*op) > end)
159 			return false;
160 
161 		if ((const uint8_t *)op + sizeof(*op) + op->size > end)
162 			return false;
163 
164 		switch (op->tag)
165 		{
166 		case PKGCONF_BYTECODE_OP_TEXT:
167 			/* this only fails due to truncation */
168 			if (!pkgconf_bytecode_eval_append_slice(ctx, out, op->data, op->size))
169 				return false;
170 			break;
171 
172 		case PKGCONF_BYTECODE_OP_VAR:
173 			if (!pkgconf_bytecode_eval_var(ctx, op->data, op->size, out, saw_sysroot))
174 				return false;
175 			break;
176 
177 		case PKGCONF_BYTECODE_OP_SYSROOT:
178 			if (saw_sysroot != NULL)
179 				*saw_sysroot = true;
180 			if (!pkgconf_bytecode_eval_append(ctx, out, pkgconf_buffer_str_or_empty(&ctx->sysroot)))
181 				return false;
182 			break;
183 
184 		default:
185 			/* reserved/unimplemented */
186 			return false;
187 		}
188 
189 		p = (const uint8_t *)pkgconf_bytecode_op_next(op);
190 	}
191 
192 	return true;
193 }
194 
195 static bool
pkgconf_bytecode_eval_ctx_init(pkgconf_bytecode_eval_ctx_t * ctx,const pkgconf_client_t * client,const pkgconf_list_t * vars)196 pkgconf_bytecode_eval_ctx_init(pkgconf_bytecode_eval_ctx_t *ctx, const pkgconf_client_t *client, const pkgconf_list_t *vars)
197 {
198 	memset(ctx, 0, sizeof(*ctx));
199 
200 	ctx->client = client;
201 	ctx->vars = vars;
202 
203 	const char *raw = pkgconf_client_get_sysroot_dir(client);
204 
205 	/* disabled sysroot cases */
206 	if (raw == NULL || *raw == '\0')
207 		return true;
208 
209 	if (raw[0] == '.' && raw[1] == '\0')
210 		return true;
211 
212 	if (raw[0] == '/' && raw[1] == '\0')
213 		return true;
214 
215 	if (!pkgconf_buffer_append(&ctx->sysroot, raw))
216 		return false;
217 
218 	while (pkgconf_buffer_len(&ctx->sysroot) > 1 && ctx->sysroot.end[-1] == '/')
219 	{
220 		if (!pkgconf_buffer_trim_byte(&ctx->sysroot))
221 			return false;
222 	}
223 
224 	/* if normalization yields "/", disable by making buffer empty */
225 	if (pkgconf_buffer_len(&ctx->sysroot) == 1 && ctx->sysroot.base[0] == '/')
226 	{
227 		if (!pkgconf_buffer_trim_byte(&ctx->sysroot))
228 			return false;
229 	}
230 
231 	return true;
232 }
233 
234 bool
pkgconf_bytecode_eval(const pkgconf_client_t * client,const pkgconf_list_t * vars,const pkgconf_bytecode_t * bc,pkgconf_buffer_t * out,bool * saw_sysroot)235 pkgconf_bytecode_eval(const pkgconf_client_t *client, const pkgconf_list_t *vars, const pkgconf_bytecode_t *bc, pkgconf_buffer_t *out, bool *saw_sysroot)
236 {
237 	bool ret;
238 
239 	if (client == NULL || bc == NULL || out == NULL)
240 		return false;
241 
242 	pkgconf_bytecode_eval_ctx_t ctx;
243 	if (!pkgconf_bytecode_eval_ctx_init(&ctx, client, vars))
244 		return false;
245 
246 	if (saw_sysroot != NULL)
247 		*saw_sysroot = false;
248 
249 	ret = pkgconf_bytecode_eval_internal(&ctx, bc, out, saw_sysroot);
250 
251 	pkgconf_buffer_finalize(&ctx.sysroot);
252 
253 	return ret;
254 }
255 
256 bool
pkgconf_bytecode_emit(pkgconf_buffer_t * buf,enum pkgconf_bytecode_op tag,const void * data,uint32_t size)257 pkgconf_bytecode_emit(pkgconf_buffer_t *buf, enum pkgconf_bytecode_op tag, const void *data, uint32_t size)
258 {
259 	pkgconf_bytecode_op_t op = {
260 		.tag = tag,
261 		.size = size,
262 	};
263 
264 	if (!pkgconf_buffer_append_slice(buf, (const char *) &op, sizeof(op)))
265 		return false;
266 
267 	if (size != 0)
268 	{
269 		if (!pkgconf_buffer_append_slice(buf, (const char *) data, (size_t) size))
270 			return false;
271 	}
272 
273 	return true;
274 }
275 
276 bool
pkgconf_bytecode_emit_text(pkgconf_buffer_t * buf,const char * p,size_t n)277 pkgconf_bytecode_emit_text(pkgconf_buffer_t *buf, const char *p, size_t n)
278 {
279 	if (p == NULL || n == 0)
280 		return true;
281 
282 	return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_TEXT, p, (uint32_t) n);
283 }
284 
285 bool
pkgconf_bytecode_emit_var(pkgconf_buffer_t * buf,const char * name,size_t nlen)286 pkgconf_bytecode_emit_var(pkgconf_buffer_t *buf, const char *name, size_t nlen)
287 {
288 	if (name == NULL || nlen == 0)
289 		return true;
290 
291 	return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_VAR, name, (uint32_t) nlen);
292 }
293 
294 bool
pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t * buf)295 pkgconf_bytecode_emit_sysroot(pkgconf_buffer_t *buf)
296 {
297 	return pkgconf_bytecode_emit(buf, PKGCONF_BYTECODE_OP_SYSROOT, NULL, 0);
298 }
299 
300 void
pkgconf_bytecode_from_buffer(pkgconf_bytecode_t * bc,const pkgconf_buffer_t * buf)301 pkgconf_bytecode_from_buffer(pkgconf_bytecode_t *bc, const pkgconf_buffer_t *buf)
302 {
303 	bc->base = (const uint8_t *)buf->base;
304 	bc->len = (size_t)(buf->end - buf->base);
305 }
306 
307 bool
pkgconf_bytecode_compile(pkgconf_buffer_t * out,const char * value)308 pkgconf_bytecode_compile(pkgconf_buffer_t *out, const char *value)
309 {
310 	const char *p, *text_start;
311 
312 	if (out == NULL || value == NULL)
313 		return false;
314 
315 	p = value;
316 	text_start = value;
317 
318 	for (; *p != '\0'; p++)
319 	{
320 		const char *name, *q;
321 
322 		if (*p != '$')
323 			continue;
324 
325 		/* $$ escapes to a literal $ */
326 		if (p[1] == '$')
327 		{
328 			if (p > text_start)
329 			{
330 				if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
331 					return false;
332 			}
333 
334 			if (!pkgconf_bytecode_emit_text(out, "$", 1))
335 				return false;
336 
337 			p++;
338 			text_start = p + 1;
339 			continue;
340 		}
341 
342 		if (p[1] != '{')
343 			continue;
344 
345 		if (p > text_start)
346 		{
347 			if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
348 				return false;
349 		}
350 
351 		name = p + 2;
352 		q = name;
353 
354 		for (; *q != '\0' && *q != '}'; q++)
355 			;
356 
357 		/* make sure a variable expansion ends with } */
358 		if (*q != '}')
359 		{
360 			text_start = p;
361 			continue;
362 		}
363 
364 		/* if this is not a valid variable, emit it as text */
365 		size_t nlen = (size_t)(q - name);
366 		if (nlen == 0 || nlen >= PKGCONF_ITEM_SIZE)
367 		{
368 			if (!pkgconf_bytecode_emit_text(out, p, (size_t)((q + 1) - p)))
369 				return false;
370 
371 			p = q;
372 			text_start = p + 1;
373 			continue;
374 		}
375 
376 		/* we need to special-case ${pc_sysrootdir} and emit OP_SYSROOT instead... */
377 		if (nlen == strlen("pc_sysrootdir") && !memcmp(name, "pc_sysrootdir", nlen))
378 		{
379 			if (!pkgconf_bytecode_emit_sysroot(out))
380 				return false;
381 		}
382 		else
383 		{
384 			if (!pkgconf_bytecode_emit_var(out, name, nlen))
385 				return false;
386 		}
387 
388 		p = q;
389 		text_start = p + 1;
390 	}
391 
392 	if (p > text_start)
393 	{
394 		if (!pkgconf_bytecode_emit_text(out, text_start, (size_t)(p - text_start)))
395 			return false;
396 	}
397 
398 	return true;
399 }
400 
401 bool
pkgconf_bytecode_eval_str_to_buf(const pkgconf_client_t * client,const pkgconf_list_t * vars,const char * input,bool * saw_sysroot,pkgconf_buffer_t * out)402 pkgconf_bytecode_eval_str_to_buf(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot, pkgconf_buffer_t *out)
403 {
404 	pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
405 	pkgconf_bytecode_t bc;
406 	bool ret = false;
407 
408 	if (!pkgconf_bytecode_compile(&bcbuf, input))
409 	{
410 		pkgconf_buffer_finalize(&bcbuf);
411 		return false;
412 	}
413 
414 	pkgconf_bytecode_from_buffer(&bc, &bcbuf);
415 
416 	ret = pkgconf_bytecode_eval(client, vars, &bc, out, saw_sysroot);
417 
418 	pkgconf_buffer_finalize(&bcbuf);
419 
420 	return ret;
421 }
422 
423 char *
pkgconf_bytecode_eval_str(const pkgconf_client_t * client,const pkgconf_list_t * vars,const char * input,bool * saw_sysroot)424 pkgconf_bytecode_eval_str(const pkgconf_client_t *client, const pkgconf_list_t *vars, const char *input, bool *saw_sysroot)
425 {
426 	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
427 
428 	if (!pkgconf_bytecode_eval_str_to_buf(client, vars, input, saw_sysroot, &out))
429 	{
430 		if (pkgconf_buffer_len(&out) > 0)
431 			return pkgconf_buffer_freeze(&out);
432 
433 		pkgconf_buffer_finalize(&out);
434 		return NULL;
435 	}
436 
437 	if (pkgconf_buffer_len(&out) == 0)
438 	{
439 		pkgconf_buffer_finalize(&out);
440 		return strdup("");
441 	}
442 
443 	return pkgconf_buffer_freeze(&out);
444 }
445 
446 bool
pkgconf_bytecode_references_var(const pkgconf_buffer_t * buf,const char * key)447 pkgconf_bytecode_references_var(const pkgconf_buffer_t *buf, const char *key)
448 {
449 	const uint8_t *p, *end;
450 	size_t klen;
451 
452 	if (buf == NULL || key == NULL)
453 		return false;
454 
455 	klen = strlen(key);
456 	p = (uint8_t *) buf->base;
457 	end = (uint8_t *) buf->end;
458 
459 	while (p < end)
460 	{
461 		const pkgconf_bytecode_op_t *op = (const pkgconf_bytecode_op_t *)p;
462 
463 		if (p + sizeof(*op) > end)
464 			return false;
465 
466 		if (p + sizeof(*op) + op->size > end)
467 			return false;
468 
469 		if (op->tag == PKGCONF_BYTECODE_OP_VAR)
470 		{
471 			if (op->size == (uint32_t) klen && memcmp(op->data, key, klen) == 0)
472 				return true;
473 		}
474 
475 		p += sizeof(*op) + op->size;
476 	}
477 
478 	return false;
479 }
480 
481 static bool
pkgconf_bytecode_op_is_selfref(const pkgconf_bytecode_op_t * op,const char * key)482 pkgconf_bytecode_op_is_selfref(const pkgconf_bytecode_op_t *op, const char *key)
483 {
484 	const size_t klen = strlen(key);
485 
486 	if (op->tag != PKGCONF_BYTECODE_OP_VAR)
487 		return false;
488 
489 	if (op->size != (uint32_t) klen)
490 		return false;
491 
492 	return memcmp(op->data, key, klen) == 0;
493 }
494 
495 static bool
pkgconf_bytecode_append_stream(pkgconf_buffer_t * dst,const pkgconf_buffer_t * bcbuf)496 pkgconf_bytecode_append_stream(pkgconf_buffer_t *dst, const pkgconf_buffer_t *bcbuf)
497 {
498 	if (dst == NULL || bcbuf == NULL || pkgconf_buffer_str(bcbuf) == NULL)
499 		return true;
500 
501 	return pkgconf_buffer_append_slice(dst, pkgconf_buffer_str(bcbuf), pkgconf_buffer_len(bcbuf));
502 }
503 
504 bool
pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t * out,const pkgconf_buffer_t * rhs,const char * key,const pkgconf_buffer_t * prev)505 pkgconf_bytecode_rewrite_selfrefs(pkgconf_buffer_t *out, const pkgconf_buffer_t *rhs, const char *key, const pkgconf_buffer_t *prev)
506 {
507 	const uint8_t *p = (uint8_t *) rhs->base;
508 	const uint8_t *end = (uint8_t *) rhs->end;
509 
510 	while (p < end)
511 	{
512 		const pkgconf_bytecode_op_t *op = (const pkgconf_bytecode_op_t *)p;
513 
514 		if (p + sizeof(*op) > end)
515 			return false;
516 
517 		if (p + sizeof(*op) + op->size > end)
518 			return false;
519 
520 		if (pkgconf_bytecode_op_is_selfref(op, key))
521 		{
522 			if (!pkgconf_bytecode_append_stream(out, prev))
523 				return false;
524 		}
525 		else
526 		{
527 			if (!pkgconf_buffer_append_slice(out, (const char *) op, sizeof(*op) + op->size))
528 				return false;
529 		}
530 
531 		p += sizeof(*op) + op->size;
532 	}
533 
534 	return true;
535 }
536