Skip to content

Commit 63224eb

Browse files
committed
put a lock around the window-update ops
1 parent c1eea11 commit 63224eb

2 files changed

Lines changed: 73 additions & 51 deletions

File tree

connection_state.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package nebula
22

33
import (
44
"encoding/json"
5+
"log/slog"
56
"sync"
67
"sync/atomic"
78

89
"github.com/slackhq/nebula/cert"
910
"github.com/slackhq/nebula/handshake"
11+
"github.com/slackhq/nebula/header"
1012
"github.com/slackhq/nebula/noiseutil"
1113
)
1214

@@ -20,6 +22,7 @@ type ConnectionState struct {
2022
initiator bool
2123
messageCounter atomic.Uint64
2224
window *Bits
25+
decryptLock sync.Mutex
2326
writeLock sync.Mutex
2427
}
2528

@@ -54,3 +57,59 @@ func (cs *ConnectionState) MarshalJSON() ([]byte, error) {
5457
func (cs *ConnectionState) Curve() cert.Curve {
5558
return cs.myCert.Curve()
5659
}
60+
61+
func (cs *ConnectionState) Decrypt(l *slog.Logger, messageCounter uint64, out []byte, packet []byte, nb []byte) ([]byte, error) {
62+
var err error
63+
cs.decryptLock.Lock()
64+
result := cs.window.Check(l, messageCounter)
65+
cs.decryptLock.Unlock()
66+
if !result {
67+
return nil, ErrAlreadySeen
68+
}
69+
70+
out, err = cs.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], messageCounter, nb)
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
cs.decryptLock.Lock()
76+
result = cs.window.Update(l, messageCounter)
77+
cs.decryptLock.Unlock()
78+
if !result {
79+
return nil, ErrAlreadySeen
80+
}
81+
return out, nil
82+
}
83+
84+
func (cs *ConnectionState) VerifyRelay(l *slog.Logger, messageCounter uint64, out []byte, packet []byte, nb []byte) ([]byte, error) {
85+
cs.decryptLock.Lock()
86+
result := cs.window.Check(l, messageCounter)
87+
cs.decryptLock.Unlock()
88+
if !result {
89+
return nil, ErrAlreadySeen
90+
}
91+
92+
// The entire body is sent as AD, not encrypted.
93+
// The packet consists of a 16-byte parsed Nebula header, Associated Data-protected payload, and a trailing 16-byte AEAD signature value.
94+
// The packet is guaranteed to be at least 16 bytes at this point, b/c it got past the h.Parse() call above. If it's
95+
// otherwise malformed (meaning, there is no trailing 16 byte AEAD value), then this will result in at worst a 0-length slice
96+
// which will gracefully fail in the DecryptDanger call.
97+
signedPayload := packet[:len(packet)-cs.dKey.Overhead()]
98+
signatureValue := packet[len(packet)-cs.dKey.Overhead():]
99+
var err error
100+
out, err = cs.dKey.DecryptDanger(out, signedPayload, signatureValue, messageCounter, nb)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
cs.decryptLock.Lock()
106+
result = cs.window.Update(l, messageCounter)
107+
cs.decryptLock.Unlock()
108+
if !result {
109+
return nil, ErrAlreadySeen
110+
}
111+
112+
// Successfully validated the thing. Get rid of the Relay header.
113+
signedPayload = signedPayload[header.Len:]
114+
return signedPayload, nil
115+
}

outside.go

Lines changed: 14 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -103,26 +103,24 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
103103
}
104104

105105
// All remaining packets are encrypted
106-
ci := hostinfo.ConnectionState
107-
if !ci.window.Check(f.l, h.MessageCounter) {
108-
return
109-
}
110-
111-
// Relay packets are special
112106
if isMessageRelay {
113-
f.handleOutsideRelayPacket(hostinfo, via, out, packet, h, fwPacket, lhf, nb, q, localCache)
114-
107+
var signedPayload []byte
108+
// Relay packets are special, this branch should always early-return
109+
signedPayload, err = hostinfo.ConnectionState.VerifyRelay(f.l, h.MessageCounter, out, packet, nb)
110+
if err != nil {
111+
if f.l.Enabled(context.Background(), slog.LevelDebug) {
112+
hostinfo.logger(f.l).Debug("Failed to verify relay packet", "error", err, "from", via, "header", h)
113+
}
114+
return
115+
}
116+
f.handleOutsideRelayPacket(hostinfo, via, out, signedPayload, h, fwPacket, lhf, nb, q, localCache)
115117
return
116118
}
117119

118-
out, err = f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
120+
out, err = hostinfo.ConnectionState.Decrypt(f.l, h.MessageCounter, out, packet, nb)
119121
if err != nil {
120122
if f.l.Enabled(context.Background(), slog.LevelDebug) {
121-
hostinfo.logger(f.l).Debug("Failed to decrypt packet",
122-
"error", err,
123-
"from", via,
124-
"header", h,
125-
)
123+
hostinfo.logger(f.l).Debug("Failed to decrypt packet", "error", err, "from", via, "header", h)
126124
}
127125
return
128126
}
@@ -151,7 +149,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
151149
// No-op, useful for the Roaming and connectionManager side-effects above
152150
case header.TestRequest:
153151
//recycle the input packet ciphertext as our output buffer
154-
f.send(header.Test, header.TestReply, ci, hostinfo, out, nb, packet)
152+
f.send(header.Test, header.TestReply, hostinfo.ConnectionState, hostinfo, out, nb, packet)
155153
default:
156154
hostinfo.logger(f.l).Error("IsValidSubType was true, but unexpected test subtype seen", "from", via, "header", h)
157155
return
@@ -169,28 +167,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
169167
}
170168
}
171169

172-
func (f *Interface) handleOutsideRelayPacket(hostinfo *HostInfo, via ViaSender, out []byte, packet []byte, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, nb []byte, q int, localCache firewall.ConntrackCache) {
173-
// The entire body is sent as AD, not encrypted.
174-
// The packet consists of a 16-byte parsed Nebula header, Associated Data-protected payload, and a trailing 16-byte AEAD signature value.
175-
// The packet is guaranteed to be at least 16 bytes at this point, b/c it got past the h.Parse() call above. If it's
176-
// otherwise malformed (meaning, there is no trailing 16 byte AEAD value), then this will result in at worst a 0-length slice
177-
// which will gracefully fail in the DecryptDanger call.
178-
signedPayload := packet[:len(packet)-hostinfo.ConnectionState.dKey.Overhead()]
179-
signatureValue := packet[len(packet)-hostinfo.ConnectionState.dKey.Overhead():]
180-
var err error
181-
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, signedPayload, signatureValue, h.MessageCounter, nb)
182-
if err != nil {
183-
return
184-
}
185-
// Advance the replay window now that the frame is authenticated
186-
if !hostinfo.ConnectionState.window.Update(f.l, h.MessageCounter) {
187-
if f.l.Enabled(context.Background(), slog.LevelDebug) {
188-
hostinfo.logger(f.l).Debug("dropping out of window relay packet", "header", h)
189-
}
190-
return
191-
}
192-
// Successfully validated the thing. Get rid of the Relay header.
193-
signedPayload = signedPayload[header.Len:]
170+
func (f *Interface) handleOutsideRelayPacket(hostinfo *HostInfo, via ViaSender, out []byte, signedPayload []byte, h *header.H, fwPacket *firewall.Packet, lhf *LightHouseHandler, nb []byte, q int, localCache firewall.ConntrackCache) {
194171
// Pull the Roaming parts up here, and return in all call paths.
195172
f.handleHostRoaming(hostinfo, via)
196173
// Track usage of both the HostInfo and the Relay for the received & authenticated packet
@@ -504,20 +481,6 @@ func parseV4(data []byte, incoming bool, fp *firewall.Packet) error {
504481
return nil
505482
}
506483

507-
func (f *Interface) decrypt(hostinfo *HostInfo, mc uint64, out []byte, packet []byte, h *header.H, nb []byte) ([]byte, error) {
508-
var err error
509-
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], mc, nb)
510-
if err != nil {
511-
return nil, err
512-
}
513-
514-
if !hostinfo.ConnectionState.window.Update(f.l, mc) {
515-
return nil, ErrOutOfWindow
516-
}
517-
518-
return out, nil
519-
}
520-
521484
func (f *Interface) handleOutsideMessagePacket(hostinfo *HostInfo, out []byte, packet []byte, fwPacket *firewall.Packet, nb []byte, q int, localCache firewall.ConntrackCache) {
522485
err := newPacket(out, true, fwPacket)
523486
if err != nil {

0 commit comments

Comments
 (0)