13 June 2010 P3. 19:14:39 Vadim Zhukov wrote:
> Thank you, I got the point. Yes, my patch is intended for some simple
> case only, when writing a program for filtering is a little bit
> overkill. I understand that there is no silver bullet, and, of course,
> separate proxy app is needed in complex cases. Well, there are reasons
> having both rdr-to and relayd(8). ;)
>
> 2010/6/13, Damien Miller <[email protected]>:
> > On Sun, 13 Jun 2010, Vadim Zhukov wrote:
> >> No, no, it's me who is excluding this way. :) Moving packets
> >> through userland and reimplementing states in the app is not the
> >> simpliest, most reliable and - last but not least - fastest way,
> >> IMHO. Please prove me if I'm wrong.
> >
> > Well, in a sense, proxying is the most reliable in that it ensures
> > that there is no exploitable ambiguity of interpretation between the
> > inspector and the receiver of traffic. This is well described in
> > Ptacek and Newsham's "Insertion, Evasion, and Denial of Service:
> > Eluding Network Intrustion Detection"[1].
> >
> > AFAIK you patch doesn't seem to deal with the trivial case of where
> > the data to inspect spans more than one packet so it isn't reliable
> > even with non-adverserial traffic.

Looks like I found the solution, but I do not want to put this in current
patch for simplicity. I'll publish it here when it'll be finished, if
there are no objections.

In a few words: for states, monitor for the packet that we should inspect
and when we see it, do inspection; if inspection fails, drop the state
(abort the connection). This may add one counter field to pf_state
structure and will add two fields to pf_rule structure.

> > The fact that doing this right is exceedingly difficult is why it
> > doesn't exist in PF already.
> >
> > -d
> >
> > [1] http://www.icir.org/vern/Ptacek-Newsham-Evasion-98.ps

So here is working patch implementing basic packet inspection. Two things
I want to highlight:

1. p_len member of pf_pdesc structure was used only in TCP paths. I
   initialize now, and use every time (it's 0 by default meaning PF
   doesn't recognize protocol header). IMHO this needs more polishing
   in case of non TCP & UDP operations, but not sure about best way.

2. I added a few DIAGNOSTIC-only checks in pf_inspect() that results in
   panic() - maybe this is overkill or even those checks are not needed?

I followed blambert@'s suggestion and use m_getptr() now; tried to use
m_apply() instead but it forced polluting pf.c and produced code bigger
by 58 bytes on i386.

--
  Best wishes,
    Vadim Zhukov


Index: share/man/man5/pf.conf.5
===================================================================
RCS file: /cvs/src/share/man/man5/pf.conf.5,v
retrieving revision 1.476
diff -u -r1.476 pf.conf.5
--- share/man/man5/pf.conf.5    19 May 2010 13:51:37 -0000      1.476
+++ share/man/man5/pf.conf.5    14 Jun 2010 01:11:09 -0000
@@ -434,6 +434,33 @@
 rule that is used when a packet does not match any rules does not
 allow IP options.
 .Pp
+.It Xo
+.Ar inspect Aq Ar value
+.Ar at Aq Ar offset
+.Xc
+Tests packet contents at the
+.Ar offset
+to be equal to
+.Ar value .
+Note that offset starts after protocol header.
+.Ar value
+can be specified as plain strings, or as hexadecimal raw strings (i.e.,
+starting with "0x").
+In the latter case you can embed any special characters.
+Maximum length of
+.Ar value is 64 characters.
+.Pp
+.It Xo
+.Ar inspect Aq Ar mask
+.Ar maskop Aq Ar value
+.Ar at Aq Ar offset
+.Xc
+Same as previous, but also allows to specify a mask to applied to
+the data from packet before comparing to
+.Ar value .
+Two operations supported are "logical and" and "logical exclusive or".
+They're specified using "&" and "^" characters, respectively.
+.Pp
 .It Ar divert-packet Aq Ar port
 Used to send matching packets to
 .Xr divert 4
@@ -2643,7 +2670,8 @@
                 "nat-to" ( redirhost | "{" redirhost-list "}" )
                 [ portspec ] [ pooltype ] [ "static-port" ] |
                 [ "fastroute" | route ] |
-                [ "received-on" ( interface-name | interface-group ) ]
+                [ "received-on" ( interface-name | interface-group ) ] |
+                "inspect" inspect

 scrubopts      = scrubopt [ [ "," ] scrubopts ]
 scrubopt       = "no-df" | "min-ttl" number | "max-mss" number |
@@ -2786,6 +2814,8 @@
 upperlimit-sc  = "upperlimit" sc-spec
 sc-spec        = ( bandwidth-spec |
                  "(" bandwidth-spec number bandwidth-spec ")" )
+inspect        = [ inspect-mask ] string "at" number
+inspect-mask   = string ( "&" | "^" )
 include        = "include" filename
 .Ed
 .Sh FILES
Index: sys/net/pf.c
===================================================================
RCS file: /cvs/src/sys/net/pf.c,v
retrieving revision 1.691
diff -u -r1.691 pf.c
--- sys/net/pf.c        7 May 2010 13:33:16 -0000       1.691
+++ sys/net/pf.c        14 Jun 2010 01:11:09 -0000
@@ -230,6 +230,8 @@
                            struct pf_state_key_cmp *, u_int, struct mbuf *);
 int                     pf_src_connlimit(struct pf_state **);
 int                     pf_check_congestion(struct ifqueue *);
+int                     pf_inspect(struct pf_pdesc *, struct mbuf *,
+                           struct pf_rule *);
 int                     pf_match_rcvif(struct mbuf *, struct pf_rule *);

 extern struct pool pfr_ktable_pl;
@@ -2271,6 +2273,53 @@
 }

 int
+pf_inspect(struct pf_pdesc *pd, struct mbuf *m, struct pf_rule *r) {
+       int     i, off;
+       char    cv;
+
+       if (r->inspect_at + r->inspect_len > pd->p_len)
+               return (0);
+       off = r->inspect_at + (pd->tot_len - pd->p_len);
+       m = m_getptr(m, off, &off);
+#ifdef DIAGNOSTIC
+       if (m == NULL)
+               panic("pf_inspect: pd->tot_len or pd->p_len is invalid");
+#endif
+       for (i = 0; i < r->inspect_len; i++, off++) {
+               while (off >= m->m_len) {
+                       off += m->m_len;
+                       m = m->m_next;
+#ifdef DIAGNOSTIC
+                       if (m == NULL)
+                               panic("pf_inspect: pd->tot_len or "
+                                   "pd->p_len is invalid");
+#endif
+                       off = 0;
+               }
+               switch (r->inspect_op) {
+               case PF_INSOP_CMP:
+                       cv = m->m_data[off];
+                       break;
+               case PF_INSOP_AND:
+                       cv = m->m_data[off] & r->inspect_mask[i];
+                       break;
+               case PF_INSOP_XOR:
+                       cv = m->m_data[off] ^ r->inspect_mask[i];
+                       break;
+               default:
+#ifdef DIAGNOSTIC
+                       panic("pf_inspect: r->inspect_op=%d",
+                           r->inspect_op);
+#endif
+                       return (0);
+               }
+               if (cv != r->inspect_what[i])
+                       return (0);
+       }
+       return (1);
+}
+
+int
 pf_match_rcvif(struct mbuf *m, struct pf_rule *r)
 {
        struct ifnet *ifp = m->m_pkthdr.rcvif;
@@ -2879,6 +2928,8 @@
                        r = TAILQ_NEXT(r, entries);
                else if (r->match_tag && !pf_match_tag(m, r, &tag))
                        r = TAILQ_NEXT(r, entries);
+               else if (r->inspect_len > 0 && !pf_inspect(pd, m, r))
+                       r = TAILQ_NEXT(r, entries);
                else if (r->rcv_kif && !pf_match_rcvif(m, r))
                        r = TAILQ_NEXT(r, entries);
                else if (r->os_fingerprint != PF_OSFP_ANY &&
@@ -5629,6 +5680,7 @@
                        REASON_SET(&reason, PFRES_SHORT);
                        goto done;
                }
+               pd.p_len = pd.tot_len - off - sizeof(struct udphdr);
                pd.sport = &uh.uh_sport;
                pd.dport = &uh.uh_dport;
                action = pf_test_state_udp(&s, dir, kif, m, off, h, &pd);
Index: sys/net/pf_ioctl.c
===================================================================
RCS file: /cvs/src/sys/net/pf_ioctl.c,v
retrieving revision 1.232
diff -u -r1.232 pf_ioctl.c
--- sys/net/pf_ioctl.c  18 Jan 2010 23:52:46 -0000      1.232
+++ sys/net/pf_ioctl.c  14 Jun 2010 01:11:09 -0000
@@ -1121,6 +1121,13 @@
                                    PFR_TFLAG_ACTIVE;
                }

+               if (rule->inspect_len > 0) {
+                       if (rule->inspect_len > PF_INSPECT_SIZE)
+                               error = EINVAL;
+                       if (rule->inspect_op >= PF_INSOP_COUNT)
+                               error = EINVAL;
+               }
+
                if (error) {
                        pf_rm_rule(NULL, rule);
                        break;
Index: sys/net/pfvar.h
===================================================================
RCS file: /cvs/src/sys/net/pfvar.h,v
retrieving revision 1.309
diff -u -r1.309 pfvar.h
--- sys/net/pfvar.h     7 May 2010 13:33:16 -0000       1.309
+++ sys/net/pfvar.h     14 Jun 2010 01:11:09 -0000
@@ -646,6 +646,17 @@
                struct pf_addr          addr;
                u_int16_t               port;
        }                       divert, divert_packet;
+
+#define PF_INSPECT_SIZE                 64
+       char                     inspect_what[PF_INSPECT_SIZE];
+       char                     inspect_mask[PF_INSPECT_SIZE];
+       u_int32_t                inspect_at;
+       u_int16_t                inspect_len;
+#define PF_INSOP_CMP           0
+#define PF_INSOP_AND           1
+#define PF_INSOP_XOR           2
+#define PF_INSOP_COUNT         3
+       u_int16_t                inspect_op;
 };

 /* rule flags */
Index: sbin/pfctl/parse.y
===================================================================
RCS file: /cvs/src/sbin/pfctl/parse.y,v
retrieving revision 1.589
diff -u -r1.589 parse.y
--- sbin/pfctl/parse.y  23 Mar 2010 13:31:29 -0000      1.589
+++ sbin/pfctl/parse.y  14 Jun 2010 01:11:09 -0000
@@ -113,6 +113,11 @@
        PFCTL_STATE_FILTER
 };

+struct rawstring {
+       char    *s;
+       size_t   len;
+};
+
 struct node_proto {
        u_int8_t                 proto;
        struct node_proto       *next;
@@ -229,6 +234,14 @@
        int                      binat;
 };

+struct inspect_opts {
+       char            what[PF_INSPECT_SIZE];
+       char            mask[PF_INSPECT_SIZE];
+       u_int32_t       at;
+       u_int32_t       len;
+       u_int32_t       op;
+};
+
 struct filter_opts {
        int                      marker;
 #define FOM_FLAGS      0x0001
@@ -287,6 +300,8 @@
                sa_family_t              af;
                struct pf_poolhashkey   *key;
        }                        route;
+
+       struct inspect_opts     inspect;
 } filter_opts;

 struct antispoof_opts {
@@ -436,6 +451,8 @@
                struct table_opts        table_opts;
                struct pool_opts         pool_opts;
                struct node_hfsc_opts    hfsc_opts;
+               struct rawstring         rawstring;
+               struct inspect_opts      inspect_opts;
        } v;
        int lineno;
 } YYSTYPE;
@@ -467,6 +484,7 @@
 %token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW
 %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
 %token DIVERTTO DIVERTREPLY DIVERTPACKET NATTO RDRTO RECEIVEDON NE LE GE
+%token INSPECT AT
 %token <v.string>              STRING
 %token <v.number>              NUMBER
 %token <v.i>                   PORTBINARY
@@ -515,6 +533,8 @@
 %type  <v.scrub_opts>          scrub_opts scrub_opt scrub_opts_l
 %type  <v.table_opts>          table_opts table_opt table_opts_l
 %type  <v.pool_opts>           pool_opts pool_opt pool_opts_l
+%type  <v.inspect_opts>        inspect_op
+%type  <v.rawstring>           rawstring
 %%

 ruleset                : /* empty */
@@ -739,6 +759,39 @@
                | STRING
                ;

+rawstring      : STRING {
+                       int     i;
+                       char    cc[3];
+
+                       if (strncmp($1, "0x", 2) == 0) {
+                               $$.len = strlen($1) - 2;
+                               if ($$.len % 2) {
+                                       yyerror("invalid hex string");
+                                       YYERROR;
+                               }
+                               $$.len /= 2;
+                               $$.s = calloc($$.len, sizeof($$.s[0]));
+                               if ($$.s == NULL)
+                                       err(1, "rawstring: calloc");
+                               cc[2] = '\0';
+                               for (i = 0; i < $$.len; i++) {
+                                       cc[0] = $1[2 + (i * 2)];
+                                       cc[1] = $1[2 + (i * 2) + 1];
+                                       if (!isxdigit(cc[0]) ||
+                                           !isxdigit(cc[1])) {
+                                               yyerror("invalid hex string");
+                                               YYERROR;
+                                       }
+                                       sscanf(cc, "%hhx", $$.s + i);
+                               }
+                               free($1);
+                       } else {
+                               $$.len = strlen($1);
+                               $$.s = $1;
+                       }
+               }
+               ;
+
 varset         : STRING '=' varstring  {
                        if (pf->opts & PF_OPT_VERBOSE)
                                printf("%s = \"%s\"\n", $1, $3);
@@ -904,6 +957,17 @@
                                }
                        r.match_tag_not = $9.match_tag_not;

+                       if ($9.inspect.len > 0) {
+                               memcpy(r.inspect_what, $9.inspect.what,
+                                   $9.inspect.len);
+                               if ($9.inspect.op != PF_INSOP_CMP)
+                                       memcpy(r.inspect_mask, $9.inspect.mask,
+                                           $9.inspect.len);
+                               r.inspect_at = $9.inspect.at;
+                               r.inspect_len = $9.inspect.len;
+                               r.inspect_op = $9.inspect.op;
+                       }
+
                        decide_address_family($8.src.host, &r.af);
                        decide_address_family($8.dst.host, &r.af);

@@ -2094,6 +2158,17 @@
                        }
                        r.divert_packet.port = $8.divert_packet.port;

+                       if ($8.inspect.len > 0) {
+                               memcpy(r.inspect_what, $8.inspect.what,
+                                   $8.inspect.len);
+                               if ($8.inspect.op != PF_INSOP_CMP)
+                                       memcpy(r.inspect_mask, $8.inspect.mask,
+                                           $8.inspect.len);
+                               r.inspect_at = $8.inspect.at;
+                               r.inspect_len = $8.inspect.len;
+                               r.inspect_op = $8.inspect.op;
+                       }
+
                        expand_rule(&r, 0, $4, &$8.nat, &$8.rdr, &$8.rroute, $6,
                            $7.src_os,
                            $7.src.host, $7.src.port, $7.dst.host, $7.dst.port,
@@ -2314,6 +2389,66 @@
                        }
                        filter_opts.rcv = $2;
                }
+               | INSPECT rawstring inspect_op AT NUMBER {
+                       if ($2.len == 0) {
+                               yyerror("inspect string is empty");
+                               YYERROR;
+                       } else if ($2.len > PF_INSPECT_SIZE) {
+                               yyerror("inspect string is longer that %d 
bytes",
+                                   PF_INSPECT_SIZE);
+                               YYERROR;
+                       }
+                       memcpy(filter_opts.inspect.what, $2.s, $2.len);
+                       filter_opts.inspect.len = $2.len;
+
+                       if ($3.len > 0) {
+                               if ($3.len != $2.len) {
+                                       yyerror("inspect string and mask have "
+                                           "different length");
+                                       YYERROR;
+                               }
+                               memcpy(filter_opts.inspect.mask, $3.mask, 
$3.len);
+                       }
+                       filter_opts.inspect.op = $3.op;
+
+                       if ($5 < 0) {
+                               yyerror("inspect address cannot be negative");
+                               YYERROR;
+                       }
+                       filter_opts.inspect.at = $5;
+               }
+               ;
+
+inspect_op     : /* empty */ {
+                       $$.op = PF_INSOP_CMP;
+                       $$.len = 0;
+               }
+               | '&' rawstring {
+                       if ($2.len == 0) {
+                               yyerror("inspect mask string is empty");
+                               YYERROR;
+                       } else if ($2.len > PF_INSPECT_SIZE) {
+                               yyerror("inspect mask is longer that %d bytes",
+                                   PF_INSPECT_SIZE);
+                               YYERROR;
+                       }
+                       memcpy($$.mask, $2.s, $2.len);
+                       $$.len = $2.len;
+                       $$.op = PF_INSOP_AND;
+               }
+               | '^' rawstring {
+                       if ($2.len == 0) {
+                               yyerror("inspect mask string is empty");
+                               YYERROR;
+                       } else if ($2.len > PF_INSPECT_SIZE) {
+                               yyerror("inspect mask is longer that %d bytes",
+                                   PF_INSPECT_SIZE);
+                               YYERROR;
+                       }
+                       memcpy($$.mask, $2.s, $2.len);
+                       $$.len = $2.len;
+                       $$.op = PF_INSOP_XOR;
+               }
                ;

 probability    : STRING                                {
@@ -5011,6 +5146,7 @@
                { "anchor",             ANCHOR},
                { "antispoof",          ANTISPOOF},
                { "any",                ANY},
+               { "at",                 AT},
                { "bandwidth",          BANDWIDTH},
                { "binat-to",           BINATTO},
                { "bitmask",            BITMASK},
@@ -5046,6 +5182,7 @@
                { "include",            INCLUDE},
                { "inet",               INET},
                { "inet6",              INET6},
+               { "inspect",            INSPECT},
                { "keep",               KEEP},
                { "label",              LABEL},
                { "limit",              LIMIT},
Index: sbin/pfctl/pfctl_parser.c
===================================================================
RCS file: /cvs/src/sbin/pfctl/pfctl_parser.c,v
retrieving revision 1.265
diff -u -r1.265 pfctl_parser.c
--- sbin/pfctl/pfctl_parser.c   16 May 2010 12:23:30 -0000      1.265
+++ sbin/pfctl/pfctl_parser.c   14 Jun 2010 01:11:09 -0000
@@ -1055,6 +1055,27 @@
                        print_pool(&r->route, 0, 0, r->af, PF_PASS, verbose);
                }
        }
+       if (r->inspect_len) {
+               printf(" inspect 0x");
+               for (i = 0; i < r->inspect_len; i++)
+                       printf("%02hhx", r->inspect_what[i]);
+               if (r->inspect_op != PF_INSOP_CMP) {
+                       switch (r->inspect_op) {
+                       case PF_INSOP_AND:
+                               printf(" & 0x");
+                               break;
+                       case PF_INSOP_XOR:
+                               printf(" ^ 0x");
+                               break;
+                       default:
+                               errx(1, "\nUnknown inspect operation %d",
+                                   r->inspect_op);
+                       }
+                       for (i = 0; i < r->inspect_len; i++)
+                               printf("%02hhx", r->inspect_mask[i]);
+               }
+               printf(" at %u", (unsigned)r->inspect_at);
+       }
 }

 void

Reply via email to