On Fri, Jan 07, 2005 at 11:06:18AM -0500, Dave Anderson wrote:

> I hadn't realized it until I started thinking this through, but it
> appears that, if floating state is enabled, there's no way to prevent
> PF from passing *some* spoofed packets -- though this probably isn't a
> significant issue in practice.  Perhaps the "right" way to deal with
> all of these issues is to provide more explicit control over floating
> state -- e.g., eliminate the current general floating state and add to
> each rule specifying "keep state" (or its relatives) an optional list
> of interfaces to which those states may float with a direction for each
> such interface.

The state entries contain the direction of the connection precisely to
prevent this kind of spoofing.

Let's say I allow a connection from A to B in on interface I, by
creating a state entry when the first packet of the connection (from A
to B) arrives in on I.

This state will only allow

  - incoming packets from A to B
  - outgoing packets from B to A

but not

  - incoming packets from B to A
  - outgoing packets from A to B

Let's say A is some external host and B is myself, so the firewall is an
endpoint of the connection. You clearly don't want to allow any outgoing
packets from A to B. Such packets would be spoofed (by the firewall).
Neither do you want to allow incoming packets from B to A. Such packets
would be spoofed by some external host.

The second case is where both A and B are other hosts, i.e. the firewall
is filtering a connection that merely passes through, and the firewall
is not an endpoint of the connection.

In this case, you'll have a second interface J, and see legitimate
traffic

  - incoming packets from A to B on I
  - outgoing packets from A to B on J
  - incoming packets from B to A on J
  - outgoing packets from B to A on I

Since a single state entry does not cover all four cases, you can either

  - filter statefully only on I, passing everything statelessly on J
  - filter statefully on both I and J, creating TWO state entries for
    each connection

In the second case, you'll get two state entries, passing

  1) on I, incoming from A to B, outgoing from B to A
  2) on J, outgoing from A to B, incoming from B to A

Whether these states are floating or if-bound is completely irrelevant
as long as all packets only pass through interfaces I and J. In other
words, if you make the first state above floating, that does not mean it
will match packets outgoing from A to B or incoming from B to A. You
still need a second state for those packets. That's what's surprising to
some users.

If you make the first state

  1) on I, incoming from A to B, outgoing from B to A

floating, that merely makes it ignore the interface, becoming

  1) on any interface, incoming from A to B, outgoing from B to A

Packets outgoing from A to B (or incoming from B to A) on any interface
still do not match this state.

A floating state is not a 'pass-through' permission for a connection, in
the sense that you allow a connection to pass through your firewall on
any path possible. You always need a second state for that (unless you
pass packets on some interfaces statelessly).

The purpose of floating states is setups where packets of one connection
may legitimately flow through more than one pair of interfaces, like
when you have dynamic routing.

For instance, you might have two uplink interfaces I1 and I2. If a
connection comes in on I1 (you create a state for a packet from A to B
incoming on I1), you might want to allow

  - incoming packets from A to B
  - outgoing packets from B to A

on I1 and I2, so when one uplink dies (or your routing table changes),
the connection initiated through I1 continues to work through I2.

Hence, you'd make the state floating (instead of if-bound to I1).

Instead of binding a state to a single interface or floating among all
interfaces, you can also bind it to a group of interfaces (group-bound),
like make I1 and I2 an interface group and bind the state to that group
only.

But all of this is orthogonal to the fact that you still need a
second state if the connection is forwarded THROUGH your firewall and
you want to allow that. One state (even if floating) will only cover one
side (which might include multiple interfaces), the side facing the
first peer of the connection. If you chose to allow the connection to
get forwarded to a second peer, you create a second state for that side
of the connection (again, possibly through multiple interfaces).

This might sound overly complicated. You might want to create a single
state entry that allows passage of the connection THROUGH the firewall,
not caring for the two sides facing the two peers. But if such a
construct would exists, it would suffer precisely the vulnerability
against some spoofed packets you mentioned. Because it doesn't exist,
the two states can deal with that case.

HTH,
Daniel

Reply via email to