pfctl: defuse `-F all -i ...', catch empty argument values

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

pfctl: defuse `-F all -i ...', catch empty argument values

Klemens Nanni-2
Limiting the "flush all" operation to a specific interface does not
make sense, and the intention was clear as well:

        pfctl.c revision 1.298
        date: 2010/06/28 23:21:41;  author: mcbride;  state: Exp;  lines: +27 -11;
        Clean up iterface stats handling:
        - 'make -Fi' reset ALL the interface statistics
             can be restricted with -i ifname
        - 'make -Fa -i ifname' fail (it's meaningless)
        - get rid of a silly little struct that's only used for one thing

        ok henning

Yet, tables and rules are always cleared:

        # pfctl -F all -i foo
        0 tables deleted.
        rules cleared
        pfctl: don't specify an interface with -Fall
        usage: pfctl ...

It even clears statistics for the empty interface:

        # pfctl -F all -i ''
        0 tables deleted.
        rules cleared
        8 states cleared
        source tracking entries cleared
        pf: statistics cleared for interface
        pf: interface flags reset

That's unacceptable.

Currently, the idiom `opt && *opt' is used to test for table, key and
interface arguments, but is inherently unable to detect empy values
since the NULL initialised `opt' is unconditionally set to `optarg'
which may be empty.

Diff below bails out immediately when `-i ...' is passed and hoists
empty option argument checks into the getopt(3) routine.  All code using
these arguments can now rely on them not being empty if given.

        $ ./obj/pfctl -F all -i ''
        pfctl: empty interface name
        # ./obj/pfctl -F all -i foo
        pfctl: don't specify an interface with -Fall
        usage: pfctl ...

Feedback? OK?

Regress passes.

NB: parse.y does accept empty strings for anchors, tables, interfaces
and tables and handles them poorly, but that's stuff for another diff.

Index: pfctl.c
===================================================================
RCS file: /cvs/src/sbin/pfctl/pfctl.c,v
retrieving revision 1.362
diff -u -p -r1.362 pfctl.c
--- pfctl.c 2 Jan 2019 23:08:00 -0000 1.362
+++ pfctl.c 5 Jan 2019 19:03:07 -0000
@@ -2342,6 +2342,8 @@ main(int argc, char *argv[])
     "a:dD:eqf:F:ghi:k:K:L:Nno:Pp:R:rS:s:t:T:vV:x:z")) != -1) {
  switch (ch) {
  case 'a':
+ if (!*optarg)
+ errx(1, "emtpy anchor name");
  anchoropt = optarg;
  break;
  case 'd':
@@ -2369,6 +2371,8 @@ main(int argc, char *argv[])
  mode = O_RDWR;
  break;
  case 'i':
+ if (!*optarg)
+ errx(1, "empty interface name");
  ifaceopt = optarg;
  break;
  case 'k':
@@ -2377,6 +2381,8 @@ main(int argc, char *argv[])
  usage();
  /* NOTREACHED */
  }
+ if (!*optarg)
+ errx(1, "empty state key");
  state_kill[state_killers++] = optarg;
  mode = O_RDWR;
  break;
@@ -2386,6 +2392,8 @@ main(int argc, char *argv[])
  usage();
  /* NOTREACHED */
  }
+ if (!*optarg)
+ errx(1, "empty source tracking key");
  src_node_kill[src_node_killers++] = optarg;
  mode = O_RDWR;
  break;
@@ -2434,6 +2442,8 @@ main(int argc, char *argv[])
  }
  break;
  case 't':
+ if (!*optarg)
+ errx(1, "empty table name");
  tableopt = optarg;
  break;
  case 'T':
@@ -2626,13 +2636,13 @@ main(int argc, char *argv[])
  pfctl_clear_stats(dev, ifaceopt, opts);
  break;
  case 'a':
- pfctl_clear_tables(anchorname, opts);
- pfctl_clear_rules(dev, opts, anchorname);
- if (ifaceopt && *ifaceopt) {
+ if (ifaceopt) {
  warnx("don't specify an interface with -Fall");
  usage();
  /* NOTREACHED */
  }
+ pfctl_clear_tables(anchorname, opts);
+ pfctl_clear_rules(dev, opts, anchorname);
  if (!*anchorname) {
  pfctl_clear_states(dev, ifaceopt, opts);
  pfctl_clear_src_nodes(dev, opts);

Reply | Threaded
Open this post in threaded view
|

Re: pfctl: defuse `-F all -i ...', catch empty argument values

Klemens Nanni-2
On Sat, Jan 05, 2019 at 12:07:59PM -0700, Theo de Raadt wrote:
> +                       if (!*optarg)
>
> I despise this idiom.  You are checking for a zero-length string.
> But you are hiding what is going on.
Because the value is used in many places. Some check for nullity, some
check for emptyness, others just strlcpy() it and leave error checks to
the ioctls.

> But why do you do it at every step??? Why not do it at the end
> after getopt?
How so?

This won't work:

        while (ch = getopt(...)) {
                switch (ch) {
                        ...
                }
                /* breaks `-v' et al. unless I check `ch' again */
                if (!*optarg)
                        errx(1, "empty -%c argument", ch);
        }

And this is the same check but just scattered and deferred really:

        while (...) {
                ...
        }
        ...
        if (ifaceopt && !*ifaceopt)
                errx(1, "empty interface name");
        ...
        memset(anchorname, 0, sizeof(anchorname));
        if (anchoropt != NULL) {
                if (!*anchoropt)
                        errx(1. "empty anchor name");
                ...
        }
        ...

> Your diff feels wrong.
Well, it's an effective solution to multiple deficiencies that does not
require major code reshuffling.

I can work on further improvements but checking these tings as early as
possible is one step in the right direction, I think.

Reply | Threaded
Open this post in threaded view
|

Re: pfctl: defuse `-F all -i ...'

Klemens Nanni-2
In reply to this post by Klemens Nanni-2
On Sat, Jan 05, 2019 at 08:04:07PM +0100, Klemens Nanni wrote:
> Diff below bails out immediately when `-i ...' is passed
Just that now.

Ignore the option argument if the option was passed since that already
fulfills our error condition of passing `-i ...' with `-F all'.

`ifaceopt' is global and therefore NULL initialised.

Still clearing tables and rules when this error is hit seems wrong to
me, so don't do anything unless the pfctl command is actually valid.


mcbride introduced this code with r1.298 in 2010 but used

        if (*ifaceopt) {

only to have stsp fix a segfault in r1.299 by changing it to the current
form.

One might as well assume that my proposed condition was the originally
intended behaviour after all and stsp's fix should've just removed the
derefence.

OK?

Index: pfctl.c
===================================================================
RCS file: /cvs/src/sbin/pfctl/pfctl.c,v
retrieving revision 1.362
diff -u -p -r1.362 pfctl.c
--- pfctl.c 2 Jan 2019 23:08:00 -0000 1.362
+++ pfctl.c 5 Jan 2019 22:01:56 -0000
@@ -2626,13 +2626,13 @@ main(int argc, char *argv[])
  pfctl_clear_stats(dev, ifaceopt, opts);
  break;
  case 'a':
- pfctl_clear_tables(anchorname, opts);
- pfctl_clear_rules(dev, opts, anchorname);
- if (ifaceopt && *ifaceopt) {
+ if (ifaceopt) {
  warnx("don't specify an interface with -Fall");
  usage();
  /* NOTREACHED */
  }
+ pfctl_clear_tables(anchorname, opts);
+ pfctl_clear_rules(dev, opts, anchorname);
  if (!*anchorname) {
  pfctl_clear_states(dev, ifaceopt, opts);
  pfctl_clear_src_nodes(dev, opts);

Reply | Threaded
Open this post in threaded view
|

Re: pfctl: defuse `-F all -i ...'

Alexandr Nedvedicky
Hello,

</snip>

> mcbride introduced this code with r1.298 in 2010 but used
>
> if (*ifaceopt) {
>
> only to have stsp fix a segfault in r1.299 by changing it to the current
> form.
>
> One might as well assume that my proposed condition was the originally
> intended behaviour after all and stsp's fix should've just removed the
> derefence.
>
> OK?

OK sashan@

>
> Index: pfctl.c
> ===================================================================
> RCS file: /cvs/src/sbin/pfctl/pfctl.c,v
> retrieving revision 1.362
> diff -u -p -r1.362 pfctl.c
> --- pfctl.c 2 Jan 2019 23:08:00 -0000 1.362
> +++ pfctl.c 5 Jan 2019 22:01:56 -0000
> @@ -2626,13 +2626,13 @@ main(int argc, char *argv[])
>   pfctl_clear_stats(dev, ifaceopt, opts);
>   break;
>   case 'a':
> - pfctl_clear_tables(anchorname, opts);
> - pfctl_clear_rules(dev, opts, anchorname);
> - if (ifaceopt && *ifaceopt) {
> + if (ifaceopt) {
>   warnx("don't specify an interface with -Fall");
>   usage();
>   /* NOTREACHED */
>   }
> + pfctl_clear_tables(anchorname, opts);
> + pfctl_clear_rules(dev, opts, anchorname);
>   if (!*anchorname) {
>   pfctl_clear_states(dev, ifaceopt, opts);
>   pfctl_clear_src_nodes(dev, opts);
>