Skip to content

Conversation

matzf
Copy link
Contributor

@matzf matzf commented Mar 2, 2022

Add egress filtering on the source IA to mitigate accidental or
malicious source address spoofing by untrusted end-hosts.
Comparing the source IA against the configured local IA for egress
packets is fast and reliable.

A malicious end host could bypass this source IA check relatively
easily; by prepending fake hop-fields, it can masquerade its outgoing
packets as transit packets, for which the check is not applied.
To mitigate this form of spoofing attack, we add a check to for the
source IP address of transit traffic -- if this source IP doesn't match
the IP address of the corresponding sibling border router, the packet is
silently dropped.
Under the reasonable assumption that the underlying (IP-)network
infrastructure prevents untrusted end-hosts from spoofing IP addresses
of the SCION infrastructure hosts, this check prevents source IA
spoofing.

As a natural extension of the source IA checks for outgoing traffic,
we also add checks for source/destination IA for transit and incoming packets;

  • for transit traffic:
    neither source nor destination IA must equal local IA
  • for incoming packets:
    source IA must NOT equal local IA
    destination IA must equal local IA
  • for outgoing packets:
    source IA must equal local IA
    destination IA must NOT equal local IA

If any check fails, we reply with the appropriate SCMP Error
InvalidSourceAddress/InvalidDestinationAddress.
Observe that in case of InvalidSourceAddress, the correct destination IA
for the SCMP Error message is unknown. It's thus possible that the error
message will be dropped because of an invalid destination IA on the
way back to the originator.

We have all information needed to extend these source/destination IA
checks to packets that originate from or are destined to direct neighbors.
However, this would not provide additional tamper-resistance; if the
neighbor AS is benign, it will perform filtering on its own. In case the
neighbor AS is malicious, it could trivially bypass such a filter by
prepending fake hop fields.

Add router acceptance test cases for these new checks.
Multiple existing test cases needed to be adapted because the input
packet accidentally failed some of the new checks.


This change is Reviewable

@matzf matzf force-pushed the router-ia-check branch 3 times, most recently from ac78e61 to 61437fe Compare March 22, 2022 14:15
Copy link
Contributor

@FR4NK-W FR4NK-W left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 10 files reviewed, 1 unresolved discussion (waiting on @FR4NK-W and @matzf)


router/dataplane.go, line 1265 at r1 (raw file):

	if p.path.IsXover() {
		if r, err := p.doXover(); err != nil {

Should the validateHopExpiry and verifyCurrentMAC checks done after a Xover info and HF update also be moved here with the other validations?

Code quote:

p.doXover()

@matzf matzf force-pushed the router-ia-check branch from 61437fe to 9fac2e9 Compare April 8, 2022 11:49
Copy link
Contributor Author

@matzf matzf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 10 files reviewed, 1 unresolved discussion (waiting on @FR4NK-W)


router/dataplane.go, line 1265 at r1 (raw file):

Previously, FR4NK-W wrote…

Should the validateHopExpiry and verifyCurrentMAC checks done after a Xover info and HF update also be moved here with the other validations?

Good point, thanks! Done.

@matzf matzf force-pushed the router-ia-check branch 3 times, most recently from 9af31f4 to c42e0eb Compare April 20, 2022 12:34
@matzf matzf force-pushed the router-ia-check branch from c42e0eb to 2f14b01 Compare May 13, 2022 08:38
@matzf matzf force-pushed the router-ia-check branch 3 times, most recently from e5058c9 to ec40080 Compare June 2, 2022 14:42
@matzf matzf requested a review from lukedirtwalker June 7, 2022 08:23
Copy link
Contributor

@oncilla oncilla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 10 files reviewed, 3 unresolved discussions (waiting on @lukedirtwalker and @matzf)


router/dataplane.go line 110 at r4 (raw file):

var (
	alreadySet                    = serrors.New("already set")
	invalidSrcIA                  = serrors.New("invalid source IA")

Suggestion:

ISD-AS

router/dataplane.go line 111 at r4 (raw file):

	alreadySet                    = serrors.New("already set")
	invalidSrcIA                  = serrors.New("invalid source IA")
	invalidDstIA                  = serrors.New("invalid destination IA")

Suggestion:

ISD-AS

router/dataplane.go line 915 at r4 (raw file):

		pktIngressID := p.ingressInterface()
		expectedSrc, ok := p.d.internalNextHops[pktIngressID]
		if !ok || !expectedSrc.IP.Equal(p.srcAddr.IP) || expectedSrc.Port != p.srcAddr.Port {

I think you should only check the IP address, but not the port here.

Checking the port prohibits source port randomization that could be used for load balancing (e.g., in LAG or ECMP)

Copy link
Contributor Author

@matzf matzf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 10 files reviewed, 3 unresolved discussions (waiting on @lukedirtwalker and @oncilla)


router/dataplane.go line 110 at r4 (raw file):

var (
	alreadySet                    = serrors.New("already set")
	invalidSrcIA                  = serrors.New("invalid source IA")

Done.


router/dataplane.go line 111 at r4 (raw file):

	alreadySet                    = serrors.New("already set")
	invalidSrcIA                  = serrors.New("invalid source IA")
	invalidDstIA                  = serrors.New("invalid destination IA")

Done.


router/dataplane.go line 915 at r4 (raw file):

Previously, oncilla (Dominik Roos) wrote…

I think you should only check the IP address, but not the port here.

Checking the port prohibits source port randomization that could be used for load balancing (e.g., in LAG or ECMP)

Done, ok for me.

Just for the record, the behavior is different for the intra-AS links, where this router implementation uses a dialed UDP connection and consequently (implicitly) checks the source port as well.

@matzf matzf requested a review from oncilla October 19, 2022 07:06
Copy link
Contributor Author

@matzf matzf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 10 files reviewed, 4 unresolved discussions (waiting on @lukedirtwalker and @oncilla)

a discussion (no related file):
Ping @oncilla, is this ok for you now?


Copy link
Collaborator

@lukedirtwalker lukedirtwalker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 10 files at r1, 1 of 1 files at r6, all commit messages.
Reviewable status: 3 of 10 files reviewed, 6 unresolved discussions (waiting on @matzf and @oncilla)


pkg/slayers/path/scion/base.go line 99 at r6 (raw file):

func (s *Base) infIndexForHF(hf uint8) uint8 {
	if s.NumINF >= 2 {

I think we could just make a switch out of this:

switch {
case s.NumINF >= 1 && hf < s.PathMeta.SegLen[0]:
     return 0
case s.NumINF >= 2 && hf < s.PathMeta.SegLen[0]+s.PathMeta.SegLen[1]:
     return 1
default:
    return uint8(s.NumINF - 1)
}

router/dataplane.go line 912 at r6 (raw file):

// SrcIA checks by disguising packets as transit traffic.
func (p *scionPacketProcessor) validateTransitUnderlaySrc() (processResult, error) {
	if p.ingressID == 0 && !p.path.IsFirstHop() {

you could use an early return here, for less indentation in the remaining code.

if p.path.IsFirstHop() || p.ingressID != 0 {
    return processResult{},nil
}

@matzf matzf force-pushed the router-ia-check branch 2 times, most recently from bc59b38 to a5f1abd Compare November 28, 2022 10:27
Copy link
Contributor Author

@matzf matzf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 1 of 11 files reviewed, 6 unresolved discussions (waiting on @lukedirtwalker and @oncilla)


pkg/slayers/path/scion/base.go line 99 at r6 (raw file):

Previously, lukedirtwalker (Lukas Vogel) wrote…

I think we could just make a switch out of this:

switch {
case s.NumINF >= 1 && hf < s.PathMeta.SegLen[0]:
     return 0
case s.NumINF >= 2 && hf < s.PathMeta.SegLen[0]+s.PathMeta.SegLen[1]:
     return 1
default:
    return uint8(s.NumINF - 1)
}

Good idea, thanks!
I opted to completely omit the NumINF checks and rely entirely on SegLen. This changes the behavior for out of range hf values (returns 2 instead of NumINF-1), so that the range check needs to be done at the caller (only for IsXover, other callers already check sufficiently). IMO this makes quite a bit easier to understand, also for IsXover.


router/dataplane.go line 912 at r6 (raw file):

Previously, lukedirtwalker (Lukas Vogel) wrote…

you could use an early return here, for less indentation in the remaining code.

if p.path.IsFirstHop() || p.ingressID != 0 {
    return processResult{},nil
}

Good idea, thanks. Done.

Copy link
Collaborator

@lukedirtwalker lukedirtwalker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 2 of 2 files at r7, 1 of 1 files at r9.
Reviewable status: 3 of 11 files reviewed, 5 unresolved discussions (waiting on @matzf and @oncilla)


pkg/slayers/path/scion/base.go line 99 at r6 (raw file):

Previously, matzf (Matthias Frei) wrote…

Good idea, thanks!
I opted to completely omit the NumINF checks and rely entirely on SegLen. This changes the behavior for out of range hf values (returns 2 instead of NumINF-1), so that the range check needs to be done at the caller (only for IsXover, other callers already check sufficiently). IMO this makes quite a bit easier to understand, also for IsXover.

couldn't you run into panics if the segments is invalid? i.e. accessing the SegLen out of bounds?

Copy link
Contributor Author

@matzf matzf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 3 of 11 files reviewed, 5 unresolved discussions (waiting on @lukedirtwalker and @oncilla)


pkg/slayers/path/scion/base.go line 99 at r6 (raw file):

Previously, lukedirtwalker (Lukas Vogel) wrote…

couldn't you run into panics if the segments is invalid? i.e. accessing the SegLen out of bounds?

No, I think not; SegLen is a fixed size array [3]uint8.

matzf and others added 2 commits November 30, 2022 10:00
Add egress filtering on the source IA to mitigate accidental or
malicious source address spoofing by untrusted end-hosts.
Compring the source IA against the configured local IA for egress
packets is fast and reliable.

A malicious end host could bypass this source IA check relatively
easily; by prepending fake hop-fields, it can masquerade it's outgoing
packets as transit packets, for which the check is not applied.
To mitigate this form of spoofing attack, we add a check to for the
source IP address of transit traffic -- if this source IP doesn't match
the IP address of the corresponding sibling border router, the packet is
silently dropped.
Under reasonable the assumption that the underlying (IP-)network
infrastructure prevents untrusted end-hosts from spoofing IP addresses
of the SCION infrastructure hosts, this check prevents source IA
spoofing.

As a natural extension of the source IA checks for outgoing traffic,
we also add checks for source/destination IA for transit and incoming packets;
- for transit traffic:
  neither source nor destination IA must equal local IA
- for incoming packets:
  source IA must NOT equal local IA
  destination IA must equal local IA
- for incoming packets:
  source IA must equal local IA
  destination IA must NOT equal local IA

If any check fails, we reply with the appropriate SCMP Error
InvalidSourceAddress/InvalidDestinationAddress.
Observe that in case of InvalidSourceAddress, the correct destination IA
for the SCMP Error message is unknown. It's thus possible that the error
message will be dropped because of an invalid destination IA on the
way to the back to the originator.

We have all information needed to extend these source/destination IA
checks to packets that originate from or are destined to direct neighbors.
However, this would not provide additional tamper-resistance; if the
neighbor AS is benign, it will perform filtering on its own. In case the
neighbor AS is malicious, it could trivially bypass such a filter by
prepending fake hop fields.

Add router acceptance test cases for these new checks.
Multiple existing test cases needed to be adapted because the input
packet accidentally failed some of the new checks.
Copy link
Collaborator

@lukedirtwalker lukedirtwalker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 1 files at r8, all commit messages.
Reviewable status: 4 of 11 files reviewed, 4 unresolved discussions (waiting on @oncilla)

Copy link
Contributor

@oncilla oncilla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 6 of 10 files at r1, 1 of 2 files at r2, 1 of 1 files at r3, 1 of 2 files at r7, 1 of 1 files at r8, 1 of 1 files at r9, all commit messages.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @FR4NK-W and @lukedirtwalker)

Copy link
Collaborator

@lukedirtwalker lukedirtwalker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 5 of 10 files at r1, 1 of 2 files at r2, 1 of 1 files at r3.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @matzf)

@matzf matzf merged commit cd5f8d0 into scionproto:master Dec 2, 2022
@matzf matzf deleted the router-ia-check branch December 2, 2022 09:53
benthor pushed a commit to benthor/scion that referenced this pull request Jan 23, 2023
Add egress filtering on the source IA to mitigate accidental or
malicious source address spoofing by untrusted end-hosts.
Comparing the source IA against the configured local IA for egress
packets is fast and reliable.

A malicious end host could bypass this source IA check relatively
easily; by prepending fake hop-fields, it can masquerade its outgoing
packets as transit packets, for which the check is not applied.
To mitigate this form of spoofing attack, we add a check to for the
source IP address of transit traffic -- if this source IP doesn't match
the IP address of the corresponding sibling border router, the packet is
silently dropped.
Under the reasonable assumption that the underlying (IP-)network
infrastructure prevents untrusted end-hosts from spoofing IP addresses
of the SCION infrastructure hosts, this check prevents source IA
spoofing.

As a natural extension of the source IA checks for outgoing traffic,
we also add checks for source/destination IA for transit and incoming packets;
- for transit traffic:
  neither source nor destination IA must equal local IA
- for incoming packets:
  source IA must NOT equal local IA
  destination IA must equal local IA
- for outgoing packets:
  source IA must equal local IA
  destination IA must NOT equal local IA

If any check fails, we reply with the appropriate SCMP Error
InvalidSourceAddress/InvalidDestinationAddress.
Observe that in case of InvalidSourceAddress, the correct destination IA
for the SCMP Error message is unknown. It's thus possible that the error
message will be dropped because of an invalid destination IA on the
way back to the originator.

We have all information needed to extend these source/destination IA
checks to packets that originate from or are destined to direct neighbors.
However, this would not provide additional tamper-resistance; if the
neighbor AS is benign, it will perform filtering on its own. In case the
neighbor AS is malicious, it could trivially bypass such a filter by
prepending fake hop fields.

Add router acceptance test cases for these new checks.
Multiple existing test cases needed to be adapted because the input
packet accidentally failed some of the new checks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants