Files
finit/libink/marshal.c
T
Joachim Wiberg 3231ade38a dbus: fixes from a code review of the branch
A pass over the whole branch before merge, mostly in libink since
that is the new code and the part exposed to the wire.  Grouped here
rather than scattered so the review is easy to read in one place.

libink parser and dispatch:

 - Bound reader lengths so a 32-bit size_t can't wrap a wire length
   past the guard and read out of bounds.  Reachable pre-auth on any
   bus, so it matters on the 32-bit targets Finit runs on.
 - Drop a peer when a reply send fails instead of limping on with a
   half-written frame; a built-in whose send failed used to fall
   through and put a second frame on the wire.

initctl:

 - Copy a D-Bus error name out of the reply before closing the client;
   the reply points into memory the close frees.  Both error paths now
   share one helper so this can't creep back.

Authorization:

 - Take the caller's groups from the kernel (SO_PEERCRED plus
   SO_PEERGROUPS) rather than getpwuid()/getgrouplist(), which go
   through NSS and can block PID 1 on a slow LDAP or SSSD backend.
   The check is now a lookup against the group resolved once at init,
   with no NSS and no 256 KiB array on the stack.  A caller reaching
   us through a broker carries no group set, so system-bus privileged
   methods are root-only; the local bus keeps group support.  See
   libink/README.md for the note on lifting that.

Shutdown:

 - Call dbus_exit() from the shutdown path so the server, its peers,
   and the socket are let go cleanly.  The teardown existed but nobody
   called it.

Tests, CI, docs:

 - A fuzz target for the message parser, run as a quick sweep in the
   suite and properly under libFuzzer in CI, with the corpus carried
   between runs.  The -as-uid tests drop groups the way a login does
   so SO_PEERGROUPS sees the right set, and widen the test socket to
   reach the per-method check behind the 0660 gate.  Bring the GitHub
   actions up to versions that run on Node 24, and tidy a few small
   things a /simplify pass turned up.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 08:57:38 +02:00

412 lines
8.6 KiB
C

/* libink — D-Bus body marshalling (writer side).
*
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
* SPDX-License-Identifier: MIT
*/
#include <string.h>
#include <sys/types.h>
#include "marshal.h"
#define ALIGN_UP(x, n) (((x) + (n) - 1) & ~((size_t)((n) - 1)))
void __w_init(struct link_writer *w, uint8_t *buf, size_t cap)
{
w->buf = buf;
w->cap = cap;
w->off = 0;
w->err = 0;
w->array_depth = 0;
}
ssize_t __w_finish(struct link_writer *w)
{
if (w->err || w->array_depth != 0)
return -1;
return (ssize_t)w->off;
}
static int reserve(struct link_writer *w, size_t align, size_t bytes)
{
size_t pad;
if (w->err)
return -1;
pad = ALIGN_UP(w->off, align) - w->off;
if (w->off + pad + bytes > w->cap) {
w->err = 1;
return -1;
}
while (pad-- > 0)
w->buf[w->off++] = 0;
return 0;
}
static void put_u32_at(struct link_writer *w, size_t pos, uint32_t v)
{
w->buf[pos] = (uint8_t)(v & 0xff);
w->buf[pos + 1] = (uint8_t)((v >> 8) & 0xff);
w->buf[pos + 2] = (uint8_t)((v >> 16) & 0xff);
w->buf[pos + 3] = (uint8_t)((v >> 24) & 0xff);
}
static void put_u32(struct link_writer *w, uint32_t v)
{
put_u32_at(w, w->off, v);
w->off += 4;
}
void __w_byte(struct link_writer *w, uint8_t v)
{
if (reserve(w, 1, 1) < 0)
return;
w->buf[w->off++] = v;
}
void __w_bool(struct link_writer *w, int v)
{
if (reserve(w, 4, 4) < 0)
return;
put_u32(w, v ? 1u : 0u);
}
void __w_u32(struct link_writer *w, uint32_t v)
{
if (reserve(w, 4, 4) < 0)
return;
put_u32(w, v);
}
static void write_lenprefixed(struct link_writer *w, const char *s, int onebyte_len)
{
size_t len = s ? strlen(s) : 0;
if (onebyte_len) {
if (reserve(w, 1, 1 + len + 1) < 0)
return;
w->buf[w->off++] = (uint8_t)len;
} else {
if (reserve(w, 4, 4 + len + 1) < 0)
return;
put_u32(w, (uint32_t)len);
}
if (s && len)
memcpy(w->buf + w->off, s, len);
w->off += len;
w->buf[w->off++] = 0;
}
void __w_string(struct link_writer *w, const char *s) { write_lenprefixed(w, s, 0); }
void __w_path (struct link_writer *w, const char *s) { write_lenprefixed(w, s, 0); }
void __w_sig (struct link_writer *w, const char *s) { write_lenprefixed(w, s, 1); }
/* Variant "v" containing a string. Wire form:
* 1-byte sig length (1), 's', NUL, then the string per __w_string. */
void __w_variant_string(struct link_writer *w, const char *s)
{
__w_sig (w, "s");
__w_string(w, s);
}
static size_t element_align(char c)
{
switch (c) {
case 'y': case 'g': case 'v': return 1;
case 'n': case 'q': return 2;
case 'b': case 'i': case 'u':
case 's': case 'o': case 'h': case 'a': return 4;
case 'x': case 't': case 'd':
case '(': case '{': return 8;
default: return 1;
}
}
void __w_array_begin(struct link_writer *w, char element_sig_first_char)
{
size_t lenpos;
if (w->err)
return;
if (w->array_depth >= LINK_WRITER_MAX_NESTING) {
w->err = 1;
return;
}
if (reserve(w, 4, 4) < 0)
return;
lenpos = w->off;
put_u32(w, 0); /* placeholder */
/* Pad to the element's alignment. These pad bytes are NOT
* counted in the array length per the D-Bus spec. */
if (reserve(w, element_align(element_sig_first_char), 0) < 0)
return;
w->arrays[w->array_depth].lenpos = lenpos;
w->arrays[w->array_depth].elemstart = w->off;
w->array_depth++;
}
void __w_array_end(struct link_writer *w)
{
size_t elemstart, lenpos;
uint32_t actual;
if (w->err || w->array_depth == 0) {
w->err = 1;
return;
}
w->array_depth--;
lenpos = w->arrays[w->array_depth].lenpos;
elemstart = w->arrays[w->array_depth].elemstart;
actual = (uint32_t)(w->off - elemstart);
put_u32_at(w, lenpos, actual);
}
void __w_struct_begin(struct link_writer *w)
{
reserve(w, 8, 0);
}
void __w_struct_end(struct link_writer *w)
{
(void)w;
}
/* ---- reader ---- */
void __r_init(struct link_reader *r, const uint8_t *body, size_t len)
{
r->base = body;
r->off = 0;
r->cap = len;
r->err = 0;
}
static int r_skip_align(struct link_reader *r, size_t align)
{
size_t pad;
if (r->err)
return -1;
pad = ALIGN_UP(r->off, align) - r->off;
if (r->off + pad > r->cap) {
r->err = 1;
return -1;
}
r->off += pad;
return 0;
}
static uint32_t rd_u32(const uint8_t *p)
{
return (uint32_t)p[0]
| ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16)
| ((uint32_t)p[3] << 24);
}
int __r_byte(struct link_reader *r, uint8_t *out)
{
if (r_skip_align(r, 1) < 0 || r->off + 1 > r->cap) {
r->err = 1;
return -1;
}
*out = r->base[r->off++];
return 0;
}
int __r_u32(struct link_reader *r, uint32_t *out)
{
if (r_skip_align(r, 4) < 0 || r->off + 4 > r->cap) {
r->err = 1;
return -1;
}
*out = rd_u32(r->base + r->off);
r->off += 4;
return 0;
}
int __r_bool(struct link_reader *r, int *out)
{
uint32_t v;
if (__r_u32(r, &v) < 0)
return -1;
*out = v ? 1 : 0;
return 0;
}
static int read_string_like(struct link_reader *r, const char **out)
{
uint32_t len;
if (__r_u32(r, &len) < 0)
return -1;
/* Subtractive so a 0xffffffff length cannot wrap the sum past
* the guard on a 32-bit size_t: off <= cap always holds, and
* the string needs len content bytes plus a nul. */
if (len >= r->cap - r->off) {
r->err = 1;
return -1;
}
/* Spec requires nul terminator at base[off + len]. */
if (r->base[r->off + len] != 0) {
r->err = 1;
return -1;
}
*out = (const char *)(r->base + r->off);
r->off += (size_t)len + 1;
return 0;
}
int __r_string(struct link_reader *r, const char **out) { return read_string_like(r, out); }
int __r_path (struct link_reader *r, const char **out) { return read_string_like(r, out); }
/*
* Parse a variant's signature header, i.e. "g" wire form: 1-byte
* length, bytes, NUL. Only single-character inner signatures are
* supported. On success the cursor sits at the value and the type
* code is returned in *type.
*/
int __r_variant_begin(struct link_reader *r, char *type)
{
uint8_t sig_len;
if (r_skip_align(r, 1) < 0 || r->off + 1 > r->cap)
goto fail;
sig_len = r->base[r->off++];
if (sig_len != 1 || r->off + 2 > r->cap)
goto fail;
if (r->base[r->off + 1] != 0)
goto fail;
*type = (char)r->base[r->off];
r->off += 2;
return 0;
fail:
r->err = 1;
return -1;
}
/* Read a variant "v" expected to contain a string. Fails if the
* inner signature is anything other than "s" (returns -1, *out set
* to NULL). */
int __r_variant_string(struct link_reader *r, const char **out)
{
char type;
*out = NULL;
if (__r_variant_begin(r, &type) < 0)
return -1;
if (type != 's') {
r->err = 1;
return -1;
}
return read_string_like(r, out);
}
/*
* Skip one basic value of the given type code, as returned by
* __r_variant_begin(). Lets a{sv} consumers tolerate value types
* they don't know. Returns -1 on non-basic types.
*/
int __r_skip_basic(struct link_reader *r, char type)
{
const char *s;
uint32_t u;
uint8_t y;
switch (type) {
case 's':
case 'o':
return read_string_like(r, &s);
case 'b':
case 'u':
case 'i':
return __r_u32(r, &u);
case 'y':
return __r_byte(r, &y);
default:
r->err = 1;
return -1;
}
}
/* Read a variant "v" expected to contain a uint32. */
int __r_variant_u32(struct link_reader *r, uint32_t *out)
{
char type;
if (__r_variant_begin(r, &type) < 0)
return -1;
if (type != 'u') {
r->err = 1;
return -1;
}
return __r_u32(r, out);
}
int __r_done(const struct link_reader *r)
{
return !r->err && r->off == r->cap;
}
int __r_align(struct link_reader *r, size_t n)
{
return r_skip_align(r, n);
}
/* Begin reading an "a<T>" array. Reads the u32 byte-length prefix
* and sets *out_end to the absolute reader offset at which the array
* ends. Caller loops while r->off < *out_end. Returns -1 on a
* truncated or oversized array length. */
int __r_array_begin(struct link_reader *r, size_t *out_end)
{
uint32_t array_bytes;
if (__r_u32(r, &array_bytes) < 0)
return -1;
/* Subtractive, see read_string_like(): off <= cap always. */
if (array_bytes > r->cap - r->off) {
r->err = 1;
return -1;
}
*out_end = r->off + (size_t)array_bytes;
return 0;
}
ssize_t __marshal_va(uint8_t *body, size_t cap, const char *sig, va_list ap)
{
struct link_writer w;
const char *s;
__w_init(&w, body, cap);
for (s = sig; *s; s++) {
switch (*s) {
case 'y':
__w_byte(&w, (uint8_t)va_arg(ap, int));
break;
case 'b':
__w_bool(&w, va_arg(ap, int));
break;
case 'u':
__w_u32(&w, va_arg(ap, uint32_t));
break;
case 's':
__w_string(&w, va_arg(ap, const char *));
break;
case 'o':
__w_path(&w, va_arg(ap, const char *));
break;
default:
return -1;
}
}
return __w_finish(&w);
}