On Fri, Apr 22, 2022 at 09:03:45PM +0200, Otto Moerbeek wrote:
> On Fri, Apr 22, 2022 at 05:59:18PM +0200, Alexander Bluhm wrote:
> 
> > On Thu, Apr 21, 2022 at 09:10:02PM +0200, Alexander Bluhm wrote:
> > > The option I have ever seen in the wild is router alert.  So it may
> > > be better to allow IGMP and ICMP6 multicast if router alert is the
> > > only option in the packet.
> > 
> > This diff implements exactly that.  I have only compile tested it.
> > If we decide that is the way to go, I will adapt my pf regress.
> 
> A quick test shows that this still blocks these:
> 
> 21:00:35.009640 fe80::aef8:ccff:feca:428c > ff02::1: HBH icmp6: multicast 
> listener query v2 [|icmp6] [hlim 1]
This is v2

New diff:
- make off and end relative to opts array
- check length of IPv4 options
- fix call to pf_walk_option
- add case IP6OPT_PADN
- add case MLDV2_LISTENER_REPORT

I have written some regression tests that deal with pf IP options.

ok?

bluhm

Index: net/pf.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/net/pf.c,v
retrieving revision 1.1126
diff -u -p -r1.1126 pf.c
--- net/pf.c    17 Mar 2022 18:27:55 -0000      1.1126
+++ net/pf.c    27 Apr 2022 21:33:36 -0000
@@ -227,6 +227,8 @@ u_int16_t            pf_calc_mss(struct pf_addr *
 static __inline int     pf_set_rt_ifp(struct pf_state *, struct pf_addr *,
                            sa_family_t, struct pf_src_node **);
 struct pf_divert       *pf_get_divert(struct mbuf *);
+int                     pf_walk_option(struct pf_pdesc *, struct ip *,
+                           int, int, u_short *);
 int                     pf_walk_header(struct pf_pdesc *, struct ip *,
                            u_short *);
 int                     pf_walk_option6(struct pf_pdesc *, struct ip6_hdr *,
@@ -3956,7 +3958,7 @@ pf_test_rule(struct pf_pdesc *pd, struct
            rtable_l2(ctx.act.rtableid) != pd->rdomain)
                pd->destchg = 1;
 
-       if (r->action == PF_PASS && pd->badopts && ! r->allow_opts) {
+       if (r->action == PF_PASS && pd->badopts != 0 && ! r->allow_opts) {
                REASON_SET(&ctx.reason, PFRES_IPOPTIONS);
 #if NPFLOG > 0
                pd->pflog |= PF_LOG_FORCE;
@@ -6382,6 +6384,55 @@ pf_get_divert(struct mbuf *m)
 }
 
 int
+pf_walk_option(struct pf_pdesc *pd, struct ip *h, int off, int end,
+    u_short *reason)
+{
+       uint8_t type, length, opts[15 * 4 - sizeof(struct ip)];
+
+       KASSERT(end - off <= sizeof(opts));
+       m_copydata(pd->m, off, end - off, opts);
+       end -= off;
+       off = 0;
+
+       while (off < end) {
+               type = opts[off];
+               if (type == IPOPT_EOL)
+                       break;
+               if (type == IPOPT_NOP) {
+                       off++;
+                       continue;
+               }
+               if (off + 2 > end) {
+                       DPFPRINTF(LOG_NOTICE, "IP length opt");
+                       REASON_SET(reason, PFRES_IPOPTIONS);
+                       return (PF_DROP);
+               }
+               length = opts[off + 1];
+               if (length < 2) {
+                       DPFPRINTF(LOG_NOTICE, "IP short opt");
+                       REASON_SET(reason, PFRES_IPOPTIONS);
+                       return (PF_DROP);
+               }
+               if (off + length > end) {
+                       DPFPRINTF(LOG_NOTICE, "IP long opt");
+                       REASON_SET(reason, PFRES_IPOPTIONS);
+                       return (PF_DROP);
+               }
+               switch (type) {
+               case IPOPT_RA:
+                       SET(pd->badopts, PF_OPT_ROUTER_ALERT);
+                       break;
+               default:
+                       SET(pd->badopts, PF_OPT_OTHER);
+                       break;
+               }
+               off += length;
+       }
+
+       return (PF_PASS);
+}
+
+int
 pf_walk_header(struct pf_pdesc *pd, struct ip *h, u_short *reason)
 {
        struct ip6_ext           ext;
@@ -6393,11 +6444,20 @@ pf_walk_header(struct pf_pdesc *pd, stru
                REASON_SET(reason, PFRES_SHORT);
                return (PF_DROP);
        }
-       if (hlen != sizeof(struct ip))
-               pd->badopts++;
+       if (hlen != sizeof(struct ip)) {
+               if (pf_walk_option(pd, h, pd->off + sizeof(struct ip),
+                   pd->off + hlen, reason) != PF_PASS)
+                       return (PF_DROP);
+               /* header options which contain only padding is fishy */
+               if (pd->badopts == 0)
+                       SET(pd->badopts, PF_OPT_OTHER);
+       }
        end = pd->off + ntohs(h->ip_len);
        pd->off += hlen;
        pd->proto = h->ip_p;
+       /* IGMP packets have router alert options, allow them */
+       if (pd->proto == IPPROTO_IGMP)
+               CLR(pd->badopts, PF_OPT_ROUTER_ALERT);
        /* stop walking over non initial fragments */
        if ((h->ip_off & htons(IP_OFFMASK)) != 0)
                return (PF_PASS);
@@ -6455,7 +6515,10 @@ pf_walk_option6(struct pf_pdesc *pd, str
                        return (PF_DROP);
                }
                switch (opt.ip6o_type) {
+               case IP6OPT_PADN:
+                       break;
                case IP6OPT_JUMBO:
+                       SET(pd->badopts, PF_OPT_JUMBO);
                        if (pd->jumbolen != 0) {
                                DPFPRINTF(LOG_NOTICE, "IPv6 multiple jumbo");
                                REASON_SET(reason, PFRES_IPOPTIONS);
@@ -6480,7 +6543,11 @@ pf_walk_option6(struct pf_pdesc *pd, str
                                return (PF_DROP);
                        }
                        break;
+               case IP6OPT_ROUTER_ALERT:
+                       SET(pd->badopts, PF_OPT_ROUTER_ALERT);
+                       break;
                default:
+                       SET(pd->badopts, PF_OPT_OTHER);
                        break;
                }
                off += sizeof(opt) + opt.ip6o_len;
@@ -6494,6 +6561,7 @@ pf_walk_header6(struct pf_pdesc *pd, str
 {
        struct ip6_frag          frag;
        struct ip6_ext           ext;
+       struct icmp6_hdr         icmp6;
        struct ip6_rthdr         rthdr;
        u_int32_t                end;
        int                      hdr_cnt, fraghdr_cnt = 0, rthdr_cnt = 0;
@@ -6506,9 +6574,17 @@ pf_walk_header6(struct pf_pdesc *pd, str
        for (hdr_cnt = 0; hdr_cnt < pf_hdr_limit; hdr_cnt++) {
                switch (pd->proto) {
                case IPPROTO_ROUTING:
-               case IPPROTO_HOPOPTS:
                case IPPROTO_DSTOPTS:
-                       pd->badopts++;
+                       SET(pd->badopts, PF_OPT_OTHER);
+                       break;
+               case IPPROTO_HOPOPTS:
+                       if (pf_walk_option6(pd, h, pd->off + sizeof(ext),
+                           pd->off + (ext.ip6e_len + 1) * 8, reason)
+                           != PF_PASS)
+                               return (PF_DROP);
+                       /* option header which contains only padding is fishy */
+                       if (pd->badopts == 0)
+                               SET(pd->badopts, PF_OPT_OTHER);
                        break;
                }
                switch (pd->proto) {
@@ -6587,19 +6663,11 @@ pf_walk_header6(struct pf_pdesc *pd, str
                        /* reassembly needs the ext header before the frag */
                        if (pd->fragoff == 0)
                                pd->extoff = pd->off;
-                       if (pd->proto == IPPROTO_HOPOPTS && pd->fragoff == 0) {
-                               if (pf_walk_option6(pd, h,
-                                   pd->off + sizeof(ext),
-                                   pd->off + (ext.ip6e_len + 1) * 8, reason)
-                                   != PF_PASS)
-                                       return (PF_DROP);
-                               if (ntohs(h->ip6_plen) == 0 &&
-                                   pd->jumbolen != 0) {
-                                       DPFPRINTF(LOG_NOTICE,
-                                           "IPv6 missing jumbo");
-                                       REASON_SET(reason, PFRES_IPOPTIONS);
-                                       return (PF_DROP);
-                               }
+                       if (pd->proto == IPPROTO_HOPOPTS && pd->fragoff == 0 &&
+                           ntohs(h->ip6_plen) == 0 && pd->jumbolen != 0) {
+                               DPFPRINTF(LOG_NOTICE, "IPv6 missing jumbo");
+                               REASON_SET(reason, PFRES_IPOPTIONS);
+                               return (PF_DROP);
                        }
                        if (pd->proto == IPPROTO_AH)
                                pd->off += (ext.ip6e_len + 2) * 4;
@@ -6607,9 +6675,24 @@ pf_walk_header6(struct pf_pdesc *pd, str
                                pd->off += (ext.ip6e_len + 1) * 8;
                        pd->proto = ext.ip6e_nxt;
                        break;
+               case IPPROTO_ICMPV6:
+                       if (!pf_pull_hdr(pd->m, pd->off, &icmp6, sizeof(icmp6),
+                           NULL, reason, AF_INET6)) {
+                               DPFPRINTF(LOG_NOTICE, "IPv6 short icmp6hdr");
+                               return (PF_DROP);
+                       }
+                       /* ICMP multicast packets have router alert options */
+                       switch (icmp6.icmp6_type) {
+                       case MLD_LISTENER_QUERY:
+                       case MLD_LISTENER_REPORT:
+                       case MLD_LISTENER_DONE:
+                       case MLDV2_LISTENER_REPORT:
+                               CLR(pd->badopts, PF_OPT_ROUTER_ALERT);
+                               break;
+                       }
+                       /* FALLTHROUGH */
                case IPPROTO_TCP:
                case IPPROTO_UDP:
-               case IPPROTO_ICMPV6:
                        /* fragments may be short, ignore inner header then */
                        if (pd->fragoff != 0 && end < pd->off +
                            (pd->proto == IPPROTO_TCP ? sizeof(struct tcphdr) :
@@ -7167,7 +7250,7 @@ done:
        if (action != PF_DROP) {
                if (s) {
                        /* The non-state case is handled in pf_test_rule() */
-                       if (action == PF_PASS && pd.badopts &&
+                       if (action == PF_PASS && pd.badopts != 0 &&
                            !(s->state_flags & PFSTATE_ALLOWOPTS)) {
                                action = PF_DROP;
                                REASON_SET(&reason, PFRES_IPOPTIONS);
Index: net/pfvar_priv.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/net/pfvar_priv.h,v
retrieving revision 1.9
diff -u -p -r1.9 pfvar_priv.h
--- net/pfvar_priv.h    8 Apr 2022 18:17:24 -0000       1.9
+++ net/pfvar_priv.h    27 Apr 2022 13:57:57 -0000
@@ -180,6 +180,9 @@ struct pf_pdesc {
        u_int32_t        fragoff;       /* fragment header offset */
        u_int32_t        jumbolen;      /* length from v6 jumbo header */
        u_int32_t        badopts;       /* v4 options or v6 routing headers */
+#define PF_OPT_OTHER           0x0001
+#define PF_OPT_JUMBO           0x0002
+#define PF_OPT_ROUTER_ALERT    0x0004
 
        u_int16_t        rdomain;       /* original routing domain */
        u_int16_t        virtual_proto;

Reply via email to