# DF-2934 — m_tag_alloc silently truncates `int len` into `uint16_t m_tag_len`

**File:** `sys/kern/uipc_mbuf2.c` (pass 2, GLM 5.3)
**Severity:** Info (hardening / latent API trap) · **Confidence:** certain · **No PoC required (Info)**

## The issue

`m_tag_alloc()` (sys/kern/uipc_mbuf2.c:256-269) takes the data length as `int`
and allocates `len + sizeof(struct m_tag)` bytes, but stores the length into
`struct m_tag.m_tag_len`, which is `uint16_t` (sys/sys/mbuf.h:138-143):

```c
struct m_tag *
m_tag_alloc(uint32_t cookie, int type, int len, int mflags)
{
        struct m_tag *t;

        if (len < 0)
                return NULL;
        t = kmalloc(len + sizeof(struct m_tag), M_PACKET_TAGS, mflags);
        if (t == NULL)
                return NULL;
        t->m_tag_id = type;      /* int  -> uint16_t truncation */
        t->m_tag_len = len;      /* int  -> uint16_t truncation */
        t->m_tag_cookie = cookie;
        return t;
}
```

For any caller with `len > 0xFFFF` the allocation is correct but the recorded
length wraps (`0x10000 + n` ⇒ `m_tag_len == n`). The creator writes `len`
bytes via `m_tag_data()` while every consumer that trusts `m_tag_len`
(e.g. `m_tag_copy()` bcopy at uipc_mbuf2.c:352, ng_tag memcmp, netgraph
get-hookout responses) under-reads; conversely any consumer that sizes its own
accesses off its private constant stays consistent. The mismatch is silent.

The same applies to `type` (`int` → `uint16_t m_tag_id`) and to
`m_tag_locate(m, cookie, int type, ...)` matching
(`p->m_tag_id == type` at uipc_mbuf2.c:335 — a caller passing `type = 0x10000`
would match a stored id of 0).

## Why this is Info, not higher

* Audited every in-tree caller of `m_tag_alloc`/`m_tag_get` (21 sites): all
  pass `sizeof(struct …)` constants or an already-`uint16_t` value
  (ng_tag.c:575 passes `uint16_t tag_len`; ng_ksocket.c:1113 is bounded by
  `sa_len ≤ 255`). **No in-tree caller can exceed 0xFFFF today.**
* On x86-64 `len + sizeof(struct m_tag)` cannot wrap (`len ≤ INT_MAX` after
  the `< 0` check; kmalloc of ~2 GB fails ⇒ NULL), so there is no integer
  overflow, only the silent length/ID mismatch.
* The m_tag API is EXPORTED for use by klds (`mbuf.h:699`), so a future or
  third-party module passing an attacker-influenced `int` length gets a
  corrupted tag metadata object with no warning — hence worth the one-line
  guard.

## Recommended fix

```diff
--- a/sys/kern/uipc_mbuf2.c
+++ b/sys/kern/uipc_mbuf2.c
@@ -257,8 +257,9 @@ struct m_tag *
 m_tag_alloc(uint32_t cookie, int type, int len, int mflags)
 {
        struct m_tag *t;
 
-       if (len < 0)
+       if (len < 0 || len > 0xFFFF || type < 0 || type > 0xFFFF)
                return NULL;
        t = kmalloc(len + sizeof(struct m_tag), M_PACKET_TAGS, mflags);
```

(mirrors `struct m_tag` field widths; belt-and-braces for exported API).
