[PATCH 2/2] cipso: make cipso_v4_skbuff_delattr() fully remove the CIPSO options
Ondrej Mosnacek
omosnace at redhat.com
Wed Apr 17 13:03:09 UTC 2024
On Tue, Apr 16, 2024 at 8:39 PM Paul Moore <paul at paul-moore.com> wrote:
>
> On Apr 16, 2024 Ondrej Mosnacek <omosnace at redhat.com> wrote:
> >
> > As the comment in this function says, the code currently just clears the
> > CIPSO part with IPOPT_NOP, rather than removing it completely and
> > trimming the packet. This is inconsistent with the other
> > cipso_v4_*_delattr() functions and with CALIPSO (IPv6).
>
> This sentence above implies an equality in handling between those three
> cases that doesn't exist. IPv6 has a radically different approach to
> IP options, comparisions between the two aren't really valid.
I don't think it's that radically different. Look at
calipso_skbuff_delattr() and you will see that it already does
something very similar as cipso_v4_skbuff_delattr() after this patch.
If the CALIPSO options are the only thing in the hopopt header, it
removes it altogether. If there are other options, it removes the part
with the CALIPSO options - here it is less pedantic and replaces it
with up to 7-byte padding if the length of the CALIPSO part is not
8-byte aligned, presumably to avoid having to move the subsequent
options (if any) and adjust the padding at the end.
> Similarly,
> how we manage IPv4 options on sockets (req or otherwise) is pretty
> different from how we manage them on packets, and that is intentional.
Perhaps, but that is an implementation detail to the user. The
resulting packet content should ideally be the same regardless of
which method of injecting the options is used, when possible.
> Drop the above sentence, or provide a more detailed explanation of the
> three aproaches explaining when they can be compared and when they
> shouldn't be compared.
I'm not sure why you think they shouldn't be compared, but I can
surely be more detailed in making my case there.
> > Implement the proper option removal to make it consistent and producing
> > more optimal IP packets when there are CIPSO options set.
> >
> > Signed-off-by: Ondrej Mosnacek <omosnace at redhat.com>
> > ---
> > net/ipv4/cipso_ipv4.c | 89 ++++++++++++++++++++++++++++---------------
> > 1 file changed, 59 insertions(+), 30 deletions(-)
>
> Outside of the SELinux test suite, what testing have you done when you
> have a Linux box forwarding between a CIPSO network segment and an
> unlabeled segment? I'm specifically interested in stream based protocols
> such as TCP. Also, do the rest of the netfilter callbacks handle it okay
> if the skb changes size in one of the callbacks? Granted it has been
> *years* since this code was written (decades?), but if I recall
> correctly, at the time it was a big no-no to change the skb size in a
> netfilter callback.
I didn't test that, TBH. But all of cipso_v4_skbuff_setattr(),
calipso_skbuff_setattr(), and calipso_skbuff_delattr() already do
skb_push()/skb_pull(), so they would all be broken if that is (still?)
true. And this new cipso_v4_skbuff_delattr() doesn't do anything
w.r.t. the skb and the IP header that those wouldn't do already.
[...]
> > @@ -2246,7 +2253,8 @@ int cipso_v4_skbuff_setattr(struct sk_buff *skb,
> > */
> > int cipso_v4_skbuff_delattr(struct sk_buff *skb)
> > {
> > - int ret_val;
> > + int ret_val, cipso_len, hdr_len_actual, new_hdr_len_actual, new_hdr_len,
> > + hdr_len_delta;
>
> Please keep line lengths under 80-chars whenever possible. I know Linus
> relaxed that requirement a while ago, but I still find the 80-char limit
> to be a positive thing.
I believe the line is exactly 80 characters, so still within the
limit. Or is it < 80 instead of <= 80? Does it really matter?
>
> > struct iphdr *iph;
> > struct ip_options *opt = &IPCB(skb)->opt;
> > unsigned char *cipso_ptr;
> > @@ -2259,16 +2267,37 @@ int cipso_v4_skbuff_delattr(struct sk_buff *skb)
> > if (ret_val < 0)
> > return ret_val;
> >
> > - /* the easiest thing to do is just replace the cipso option with noop
> > - * options since we don't change the size of the packet, although we
> > - * still need to recalculate the checksum */
>
> Unless you can guarantee that the length change isn't going to have
> any adverse effects (even then I would want to know why you are so
> confident), I'd feel a lot more comfortable sticking with a
> preserve-the-size-and-fill approach. If you want to change that from
> _NOP to _END, I'd be okay with that.
>
> (and if you are talking to who I think you are talking to, I'm guessing
> the _NOP to _END swap would likely solve their problem)
So, to reveal all the cards, the issue that has prompted me to send
this patch is that a user may have a router configured to drop packets
containing any IP options [1][2] and may expect a Linux host with
NetLabel configured as unlabeled for a target IP address to
output/forward packets without IP options if CIPSO was the only option
present before NetLabel processing (such that it can then pass through
the strict router(s)).
Padding with IPOPT_END *might* solve this particular case, but I'm not
sure if the routers would really interpret such padding as "no
options"... I'll try to ask the reporter to test such a patch and
we'll see. But still, I don't yet see a convincing reason to not go
all the way and make sure we send optimally-sized packets here.
Side note: We could only clear CIPSO with IPOPT_END if it's the last
option in the header, but that limitation doesn't really matter for
the use case described above.
[1] https://www.stigviewer.com/stig/juniper_router_rtr/2019-09-27/finding/V-90937
[2] https://www.stigviewer.com/stig/cisco_ios_xe_router_rtr/2021-03-26/finding/V-217001
>
> > iph = ip_hdr(skb);
> > cipso_ptr = (unsigned char *)iph + opt->cipso;
> > - memset(cipso_ptr, IPOPT_NOOP, cipso_ptr[1]);
> > + cipso_len = cipso_ptr[1];
> > +
> > + hdr_len_actual = sizeof(struct iphdr) +
> > + cipso_v4_get_actual_opt_len((unsigned char *)(iph + 1),
> > + opt->optlen);
> > + new_hdr_len_actual = hdr_len_actual - cipso_len;
> > + new_hdr_len = (new_hdr_len_actual + 3) & ~3;
> > + hdr_len_delta = (iph->ihl << 2) - new_hdr_len;
> > +
> > + /* 1. shift any options after CIPSO to the left */
> > + memmove(cipso_ptr, cipso_ptr + cipso_len,
> > + new_hdr_len_actual - opt->cipso);
> > + /* 2. move the whole IP header to its new place */
> > + memmove((unsigned char *)iph + hdr_len_delta, iph, new_hdr_len_actual);
> > + /* 3. adjust the skb layout */
> > + skb_pull(skb, hdr_len_delta);
> > + skb_reset_network_header(skb);
> > + iph = ip_hdr(skb);
> > + /* 4. re-fill new padding with IPOPT_END (may now be longer) */
> > + memset((unsigned char *)iph + new_hdr_len_actual, IPOPT_END,
> > + new_hdr_len - new_hdr_len_actual);
> > + opt->optlen -= hdr_len_delta;
> > opt->cipso = 0;
> > opt->is_changed = 1;
> > -
> > + if (hdr_len_delta != 0) {
> > + iph->ihl = new_hdr_len >> 2;
> > + iph_set_totlen(iph, skb->len);
> > + }
> > ip_send_check(iph);
> >
> > return 0;
> > --
> > 2.44.0
>
> --
> paul-moore.com
>
--
Ondrej Mosnacek
Senior Software Engineer, Linux Security - SELinux kernel
Red Hat, Inc.
More information about the Linux-security-module-archive
mailing list