В ядре Linux устранена следующая уязвимость:
ipv6: избежать переполнения в ip6_datagram_send_ctl()
Имин Цянь сообщил:
<цитата>
Я считаю, что обнаружил локальную ошибку ядра в отправке IPv6 sendmsg.
путь вспомогательных данных, который может вызвать панику ядра через `skb_under_panic()`
(местный DoS). Основная проблема заключается в несоответствии между:
- 16-битный аккумулятор длины (`struct ipv6_txoptions::opt_flen`, введите
`__u16`) и
- указатель на *последний* предоставленный заголовок опций назначения (`opt->dst1opt`)
когда предоставлено несколько управляющих сообщений `IPV6_DSTOPTS` (cmsgs).
- `include/net/ipv6.h`:
- `struct ipv6_txoptions::opt_flen` — это `__u16` (возможен перенос).
(строки 291-307, особенно 298)
- `net/ipv6/datagram.c:ip6_datagram_send_ctl()`:
- Принимает повторяющиеся `IPV6_DSTOPTS` и накапливает их в `opt_flen`
без отклонения дубликатов. (строки 909-933)
- `net/ipv6/ip6_output.c:__ip6_append_data()`:
- Использует `opt->opt_flen + opt->opt_nflen` для вычисления заголовка.
решения по размерам/высоте. (строки 1448–1466, особенно 1463–1465)
- `net/ipv6/ip6_output.c:__ip6_make_skb()`:
- Вызывает `ipv6_push_frag_opts()`, если `opt->opt_flen` не равно нулю.
(строки 1930-1934)
- `net/ipv6/exthdrs.c:ipv6_push_frag_opts()` / `ipv6_push_exthdr()`:
- Размер push определяется `ipv6_optlen(opt->dst1opt)` (на основе
заголовок, на который указывает). (строки 1179–1185 и 1206–1211)
1. `opt_flen` — 16-битный аккумулятор:
- `include/net/ipv6.h:298` определяет `__u16 opt_flen; /* после фрагмента hdr */`.
2. `ip6_datagram_send_ctl()` принимает *повторяющиеся* `IPV6_DSTOPTS` cmsgs
и каждый раз увеличивает `opt_flen`:
- В `net/ipv6/datagram.c:909-933` для `IPV6_DSTOPTS`:
- Он вычисляет `len = ((hdr->hdrlen + 1) << 3);`
- Он проверяет CAP_NET_RAW, используя ns_capable(net->user_ns,
CAP_NET_RAW)`. (строка 922)
- Тогда это происходит:
- `opt->opt_flen += len;` (строка 927)
- `opt->dst1opt = hdr;` (строка 928)
Здесь нет дублированного отклонения (в отличие от устаревшего
Путь `IPV6_2292DSTOPTS`, который отклоняет дубликаты на
`net/ipv6/datagram.c:901-904`). Если предоставлено достаточно больших cmsg `IPV6_DSTOPTS`, `opt_flen` переносится.
в то время как `dst1opt` по-прежнему указывает на большой (2048-байтовый)
заголовок опций назначения.
В прилагаемом PoC (`poc.c`):
- 32 cmsgs с `hdrlen=255` => `len = (255+1)*8 = 2048`
- 1 cmsg с `hdrlen=0` => `len = 8`
- Общий прирост: `32*2048 + 8 = 65544`, поэтому `(__u16)opt_flen == 8`
- Последний cmsg имеет размер 2048 байт, поэтому dst1opt указывает на 2048-байтовый заголовок.
3. Заголовки размеров пути передачи с использованием обернутого `opt_flen`:
- В `net/ipv6/ip6_output.c:1463-1465`:
- `headersize = sizeof(struct ipv6hdr) + (opt ? opt->opt_flen +
opt->opt_nflen : 0) + ...;`
С обернутым `opt_flen` решения `headersize`/headroom недооцениваются
что будет выдвинуто позже.
4. При построении окончательного skb фактическая длина нажатия определяется из
`dst1opt` и не ограничивается обернутым `opt_flen`:
- В `net/ipv6/ip6_output.c:1930-1934`:
- `if (opt->opt_flen) proto = ipv6_push_frag_opts(skb, opt, proto);`
- В `net/ipv6/exthdrs.c:1206-1211` `ipv6_push_frag_opts()` помещает
dst1opt через ipv6_push_exthdr().
- В `net/ipv6/exthdrs.c:1179-1184` `ipv6_push_exthdr()` выполняет:
- `skb_push(skb, ipv6_optlen(opt));`
- `memcpy(h, opt, ipv6_optlen(opt));`
При недостаточном запасе `skb_push()` переполняется и срабатывает.
`skb_under_panic()` -> `BUG()`:
- `net/core/skbuff.c:2669-2675` (`skb_push()` вызывает `skb_under_panic()`)
- `net/core/skbuff.c:207-214` (`skb_panic()` заканчивается на `BUG()`)
- Путь cmsg `IPV6_DSTOPTS` требует `CAP_NET_RAW` в цели.
пространство имен пользователя netns (`ns_capable(net->user_ns, CAP_NET_RAW)`).
- Корень (или любая задача с `CAP_NET_RAW`) может запустить это без пользователя.
пространства имен.
- Непривилегированный пользователь `uid=1000` может вызвать это, если у него нет привилегий.
пространства имен пользователей включены, и он может создать userns+netns для получения
с пространством имен CAP_NET_RAW (это делает прилагаемый PoC).
- Локальный отказ в обслуживании: ошибка ядра/паника (сбой системы).
-
---усечено---
Показать оригинальное описание (EN)
In the Linux kernel, the following vulnerability has been resolved: ipv6: avoid overflows in ip6_datagram_send_ctl() Yiming Qian reported : <quote> I believe I found a locally triggerable kernel bug in the IPv6 sendmsg ancillary-data path that can panic the kernel via `skb_under_panic()` (local DoS). The core issue is a mismatch between: - a 16-bit length accumulator (`struct ipv6_txoptions::opt_flen`, type `__u16`) and - a pointer to the *last* provided destination-options header (`opt->dst1opt`) when multiple `IPV6_DSTOPTS` control messages (cmsgs) are provided. - `include/net/ipv6.h`: - `struct ipv6_txoptions::opt_flen` is `__u16` (wrap possible). (lines 291-307, especially 298) - `net/ipv6/datagram.c:ip6_datagram_send_ctl()`: - Accepts repeated `IPV6_DSTOPTS` and accumulates into `opt_flen` without rejecting duplicates. (lines 909-933) - `net/ipv6/ip6_output.c:__ip6_append_data()`: - Uses `opt->opt_flen + opt->opt_nflen` to compute header sizes/headroom decisions. (lines 1448-1466, especially 1463-1465) - `net/ipv6/ip6_output.c:__ip6_make_skb()`: - Calls `ipv6_push_frag_opts()` if `opt->opt_flen` is non-zero. (lines 1930-1934) - `net/ipv6/exthdrs.c:ipv6_push_frag_opts()` / `ipv6_push_exthdr()`: - Push size comes from `ipv6_optlen(opt->dst1opt)` (based on the pointed-to header). (lines 1179-1185 and 1206-1211) 1. `opt_flen` is a 16-bit accumulator: - `include/net/ipv6.h:298` defines `__u16 opt_flen; /* after fragment hdr */`. 2. `ip6_datagram_send_ctl()` accepts *repeated* `IPV6_DSTOPTS` cmsgs and increments `opt_flen` each time: - In `net/ipv6/datagram.c:909-933`, for `IPV6_DSTOPTS`: - It computes `len = ((hdr->hdrlen + 1) << 3);` - It checks `CAP_NET_RAW` using `ns_capable(net->user_ns, CAP_NET_RAW)`. (line 922) - Then it does: - `opt->opt_flen += len;` (line 927) - `opt->dst1opt = hdr;` (line 928) There is no duplicate rejection here (unlike the legacy `IPV6_2292DSTOPTS` path which rejects duplicates at `net/ipv6/datagram.c:901-904`). If enough large `IPV6_DSTOPTS` cmsgs are provided, `opt_flen` wraps while `dst1opt` still points to a large (2048-byte) destination-options header. In the attached PoC (`poc.c`): - 32 cmsgs with `hdrlen=255` => `len = (255+1)*8 = 2048` - 1 cmsg with `hdrlen=0` => `len = 8` - Total increment: `32*2048 + 8 = 65544`, so `(__u16)opt_flen == 8` - The last cmsg is 2048 bytes, so `dst1opt` points to a 2048-byte header. 3. The transmit path sizes headers using the wrapped `opt_flen`: - In `net/ipv6/ip6_output.c:1463-1465`: - `headersize = sizeof(struct ipv6hdr) + (opt ? opt->opt_flen + opt->opt_nflen : 0) + ...;` With wrapped `opt_flen`, `headersize`/headroom decisions underestimate what will be pushed later. 4. When building the final skb, the actual push length comes from `dst1opt` and is not limited by wrapped `opt_flen`: - In `net/ipv6/ip6_output.c:1930-1934`: - `if (opt->opt_flen) proto = ipv6_push_frag_opts(skb, opt, proto);` - In `net/ipv6/exthdrs.c:1206-1211`, `ipv6_push_frag_opts()` pushes `dst1opt` via `ipv6_push_exthdr()`. - In `net/ipv6/exthdrs.c:1179-1184`, `ipv6_push_exthdr()` does: - `skb_push(skb, ipv6_optlen(opt));` - `memcpy(h, opt, ipv6_optlen(opt));` With insufficient headroom, `skb_push()` underflows and triggers `skb_under_panic()` -> `BUG()`: - `net/core/skbuff.c:2669-2675` (`skb_push()` calls `skb_under_panic()`) - `net/core/skbuff.c:207-214` (`skb_panic()` ends in `BUG()`) - The `IPV6_DSTOPTS` cmsg path requires `CAP_NET_RAW` in the target netns user namespace (`ns_capable(net->user_ns, CAP_NET_RAW)`). - Root (or any task with `CAP_NET_RAW`) can trigger this without user namespaces. - An unprivileged `uid=1000` user can trigger this if unprivileged user namespaces are enabled and it can create a userns+netns to obtain namespaced `CAP_NET_RAW` (the attached PoC does this). - Local denial of service: kernel BUG/panic (system crash). - ---truncated---