Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lnwire+peer: fully validate node aliases on the wire, don't d/c due to invalid aliases #2412

Merged
merged 3 commits into from
Jan 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions lnwire/lnwire.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ func (a addressType) AddrLen() uint16 {
// serialization.
func WriteElement(w io.Writer, element interface{}) error {
switch e := element.(type) {
case NodeAlias:
if _, err := w.Write(e[:]); err != nil {
return err
}

case ShortChanIDEncoding:
var b [1]byte
b[0] = uint8(e)
Expand Down Expand Up @@ -429,6 +434,18 @@ func WriteElements(w io.Writer, elements ...interface{}) error {
func ReadElement(r io.Reader, element interface{}) error {
var err error
switch e := element.(type) {
case *NodeAlias:
var a [32]byte
if _, err := io.ReadFull(r, a[:]); err != nil {
return err
}

alias, err := NewNodeAlias(string(a[:]))
if err != nil {
return err
}

*e = alias
case *ShortChanIDEncoding:
var b [1]uint8
if _, err := r.Read(b[:]); err != nil {
Expand Down
19 changes: 12 additions & 7 deletions lnwire/lnwire_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ var (
_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
)

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func randAlias(r *rand.Rand) NodeAlias {
var a NodeAlias
for i := range a {
a[i] = letterBytes[r.Intn(len(letterBytes))]
}

return a
}

func randPubKey() (*btcec.PublicKey, error) {
priv, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
Expand Down Expand Up @@ -551,17 +562,11 @@ func TestLightningWireProtocol(t *testing.T) {
v[0] = reflect.ValueOf(req)
},
MsgNodeAnnouncement: func(v []reflect.Value, r *rand.Rand) {
var a [32]byte
if _, err := r.Read(a[:]); err != nil {
t.Fatalf("unable to generate alias: %v", err)
return
}

var err error
req := NodeAnnouncement{
Features: randRawFeatureVector(r),
Timestamp: uint32(r.Int31()),
Alias: a,
Alias: randAlias(r),
RGBColor: color.RGBA{
R: uint8(r.Int31()),
G: uint8(r.Int31()),
Expand Down
20 changes: 16 additions & 4 deletions lnwire/node_announcement.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ func (e ErrUnknownAddrType) Error() string {
return fmt.Sprintf("unknown address type: %v", e.addrType)
}

// ErrInvalidNodeAlias is an error returned if a node alias we parse on the
// wire is invalid, as in it has non UTF-8 characters.
type ErrInvalidNodeAlias struct{}

// Error returns a human readable string describing the error.
//
// NOTE: implements the error interface.
func (e ErrInvalidNodeAlias) Error() string {
return "node alias has non-utf8 characters"
}

// NodeAlias a hex encoded UTF-8 string that may be displayed as an alternative
// to the node's ID. Notice that aliases are not unique and may be freely
// chosen by the node operators.
Expand All @@ -39,11 +50,12 @@ func NewNodeAlias(s string) (NodeAlias, error) {
var n NodeAlias

if len(s) > 32 {
return n, fmt.Errorf("alias too large: max is %v, got %v", 32, len(s))
return n, fmt.Errorf("alias too large: max is %v, got %v", 32,
len(s))
}

if !utf8.ValidString(s) {
return n, fmt.Errorf("invalid utf8 string")
return n, &ErrInvalidNodeAlias{}
}

copy(n[:], []byte(s))
Expand Down Expand Up @@ -117,7 +129,7 @@ func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error {
&a.Timestamp,
&a.NodeID,
&a.RGBColor,
a.Alias[:],
&a.Alias,
&a.Addresses,
)
if err != nil {
Expand Down Expand Up @@ -149,7 +161,7 @@ func (a *NodeAnnouncement) Encode(w io.Writer, pver uint32) error {
a.Timestamp,
a.NodeID,
a.RGBColor,
a.Alias[:],
a.Alias,
a.Addresses,
a.ExtraOpaqueData,
)
Expand Down
42 changes: 42 additions & 0 deletions lnwire/node_announcement_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package lnwire

import "testing"

// TestNodeAliasValidation tests that the NewNodeAlias method will only accept
// valid node announcements.
func TestNodeAliasValidation(t *testing.T) {
t.Parallel()

var testCases = []struct {
alias string
valid bool
}{
// UTF-8 alias with valid length.
{
alias: "meruem",
valid: true,
},

// UTF-8 alias with invalid length.
{
alias: "p3kysxqr23swl33m6h5grmzddgw5nsgkky3g52zc6frpwz",
valid: false,
},

// String with non UTF-8 characters.
{
alias: "\xE0\x80\x80",
valid: false,
},
}
for i, testCase := range testCases {
_, err := NewNodeAlias(testCase.alias)
switch {
case err != nil && testCase.valid:
t.Fatalf("#%v: alias should have been invalid", i)

case err == nil && !testCase.valid:
t.Fatalf("#%v: invalid alias was missed", i)
}
}
}
7 changes: 7 additions & 0 deletions peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,13 @@ out:
idleTimer.Reset(idleTimeout)
continue

// If the NodeAnnouncement has an invalid alias, then
// we'll log that error above and continue so we can
// continue to read messges from the peer.
case *lnwire.ErrInvalidNodeAlias:
idleTimer.Reset(idleTimeout)
continue

// If the error we encountered wasn't just a message we
// didn't recognize, then we'll stop all processing s
// this is a fatal error.
Expand Down