public inbox for nncp-devel@lists.cypherpunks.ru
Atom feed
From: John Goerzen <jgoerzen@complete•org>
To: nncp-devel@lists.cypherpunks.ru
Subject: Re: Unexpected EOF in 7.2.0
Date: Fri, 09 Jul 2021 15:23:05 -0500	[thread overview]
Message-ID: <878s2fck46.fsf@complete.org> (raw)
In-Reply-To: <87a6mvcubw.fsf@complete.org>

[-- Attachment #1: Type: text/plain, Size: 881 bytes --]

I think I have a lead on this.

The packet was generated on a Raspberry Pi (32-bit ARM userland), 
is 3GB in size, and is being processed on an x86_64 machine 
(64-bit userland).

I added some debug info to pkt.go, and this was printed when -dump 
was used:

pktEncRead: Making LimitedReader, size: -1097891139
readBytes: 0, len(buf): 131088, len(toRead): 131088, EnbClkSize: 
131072, aead.Overhead: 16, doEncrypt %!d(bool=false)
main.go:163: unexpected EOF

So the presence of that LimitedReader looks to me like what caused 
the UnexpectedEof...  and if it's being called with a negative 
size, that could particularly cause it.

I did try decoding this packet on the Raspberry Pi, but got the 
same result.

Attached is the modified pkt.go that produced this output.

Hope this helps!  Hopefully whatever the issue is in nncp-pkt also 
leads to the issue in nncp-toss.

- John


[-- Attachment #2: pkt.go --]
[-- Type: application/octet-stream, Size: 9030 bytes --]

/*
NNCP -- Node to Node copy, utilities for store-and-forward data exchange
Copyright (C) 2016-2021 Sergey Matveev <stargrave@stargrave•org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package nncp

import (
	"bytes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"io"
        "fmt"

	xdr "github.com/davecgh/go-xdr/xdr2"
	"golang.org/x/crypto/chacha20poly1305"
	"golang.org/x/crypto/curve25519"
	"golang.org/x/crypto/ed25519"
	"golang.org/x/crypto/nacl/box"
	"golang.org/x/crypto/poly1305"
	"lukechampine.com/blake3"
)

type PktType uint8

const (
	EncBlkSize = 128 * (1 << 10)

	PktTypeFile    PktType = iota
	PktTypeFreq    PktType = iota
	PktTypeExec    PktType = iota
	PktTypeTrns    PktType = iota
	PktTypeExecFat PktType = iota
	PktTypeArea    PktType = iota

	MaxPathSize = 1<<8 - 1

	NNCPBundlePrefix = "NNCP"

	PktSizeOverhead = 8 + poly1305.TagSize
)

var (
	BadPktType error = errors.New("Unknown packet type")

	PktOverhead    int64
	PktEncOverhead int64
)

type Pkt struct {
	Magic   [8]byte
	Type    PktType
	Nice    uint8
	PathLen uint8
	Path    [MaxPathSize]byte
}

type PktTbs struct {
	Magic     [8]byte
	Nice      uint8
	Sender    *NodeId
	Recipient *NodeId
	ExchPub   [32]byte
}

type PktEnc struct {
	Magic     [8]byte
	Nice      uint8
	Sender    *NodeId
	Recipient *NodeId
	ExchPub   [32]byte
	Sign      [ed25519.SignatureSize]byte
}

func init() {
	pkt := Pkt{Type: PktTypeFile}
	var buf bytes.Buffer
	n, err := xdr.Marshal(&buf, pkt)
	if err != nil {
		panic(err)
	}
	PktOverhead = int64(n)
	buf.Reset()

	dummyId, err := NodeIdFromString(DummyB32Id)
	if err != nil {
		panic(err)
	}
	pktEnc := PktEnc{
		Magic:     MagicNNCPEv5.B,
		Sender:    dummyId,
		Recipient: dummyId,
	}
	n, err = xdr.Marshal(&buf, pktEnc)
	if err != nil {
		panic(err)
	}
	PktEncOverhead = int64(n)
}

func NewPkt(typ PktType, nice uint8, path []byte) (*Pkt, error) {
	if len(path) > MaxPathSize {
		return nil, errors.New("Too long path")
	}
	pkt := Pkt{
		Magic:   MagicNNCPPv3.B,
		Type:    typ,
		Nice:    nice,
		PathLen: uint8(len(path)),
	}
	copy(pkt.Path[:], path)
	return &pkt, nil
}

func ctrIncr(b []byte) {
	for i := len(b) - 1; i >= 0; i-- {
		b[i]++
		if b[i] != 0 {
			return
		}
	}
	panic("counter overflow")
}

func aeadProcess(
	aead cipher.AEAD,
	nonce, ad []byte,
	doEncrypt bool,
	r io.Reader,
	w io.Writer,
) (int, error) {
	ciphCtr := nonce[len(nonce)-8:]
	buf := make([]byte, EncBlkSize+aead.Overhead())
	var toRead []byte
	var toWrite []byte
	var n int
	var readBytes int
	var err error
	if doEncrypt {
		toRead = buf[:EncBlkSize]
	} else {
		toRead = buf
	}
	for {
                fmt.Printf("readBytes: %d, len(buf): %d, len(toRead): %d, EnbClkSize: %d, aead.Overhead: %d, doEncrypt %d\n", readBytes, len(buf), len(toRead), EncBlkSize, aead.Overhead(), doEncrypt)
		n, err = io.ReadFull(r, toRead)
		if err != nil {
			if err == io.EOF {
				break
			}
			if err != io.ErrUnexpectedEOF {
				return readBytes + n, err
			}
		}
		readBytes += n
		ctrIncr(ciphCtr)
		if doEncrypt {
			toWrite = aead.Seal(buf[:0], nonce, buf[:n], ad)
		} else {
			toWrite, err = aead.Open(buf[:0], nonce, buf[:n], ad)
			if err != nil {
				return readBytes, err
			}
		}
		if _, err = w.Write(toWrite); err != nil {
			return readBytes, err
		}
	}
	return readBytes, nil
}

func sizeWithTags(size int64) (fullSize int64) {
	fullSize = size + (size/EncBlkSize)*poly1305.TagSize
	if size%EncBlkSize != 0 {
		fullSize += poly1305.TagSize
	}
	return
}

func PktEncWrite(
	our *NodeOur,
	their *Node,
	pkt *Pkt,
	nice uint8,
	size, padSize int64,
	data io.Reader,
	out io.Writer,
) ([]byte, error) {
	pubEph, prvEph, err := box.GenerateKey(rand.Reader)
	if err != nil {
		return nil, err
	}
	var pktBuf bytes.Buffer
	if _, err := xdr.Marshal(&pktBuf, pkt); err != nil {
		return nil, err
	}
	tbs := PktTbs{
		Magic:     MagicNNCPEv5.B,
		Nice:      nice,
		Sender:    our.Id,
		Recipient: their.Id,
		ExchPub:   *pubEph,
	}
	var tbsBuf bytes.Buffer
	if _, err = xdr.Marshal(&tbsBuf, &tbs); err != nil {
		return nil, err
	}
	signature := new([ed25519.SignatureSize]byte)
	copy(signature[:], ed25519.Sign(our.SignPrv, tbsBuf.Bytes()))
	pktEnc := PktEnc{
		Magic:     MagicNNCPEv5.B,
		Nice:      nice,
		Sender:    our.Id,
		Recipient: their.Id,
		ExchPub:   *pubEph,
		Sign:      *signature,
	}
	ad := blake3.Sum256(tbsBuf.Bytes())
	tbsBuf.Reset()
	if _, err = xdr.Marshal(&tbsBuf, &pktEnc); err != nil {
		return nil, err
	}
	pktEncRaw := tbsBuf.Bytes()
	if _, err = out.Write(pktEncRaw); err != nil {
		return nil, err
	}
	sharedKey := new([32]byte)
	curve25519.ScalarMult(sharedKey, prvEph, their.ExchPub)

	key := make([]byte, chacha20poly1305.KeySize)
	blake3.DeriveKey(key, string(MagicNNCPEv5.B[:]), sharedKey[:])
	aead, err := chacha20poly1305.New(key)
	if err != nil {
		return nil, err
	}
	nonce := make([]byte, aead.NonceSize())

	fullSize := pktBuf.Len() + int(size)
	sizeBuf := make([]byte, 8+aead.Overhead())
	binary.BigEndian.PutUint64(sizeBuf, uint64(sizeWithTags(int64(fullSize))))
	if _, err = out.Write(aead.Seal(sizeBuf[:0], nonce, sizeBuf[:8], ad[:])); err != nil {
		return nil, err
	}

        fmt.Printf("pktEncWrite: Making LimitedReader, size: %d\n", size);
	lr := io.LimitedReader{R: data, N: size}
	mr := io.MultiReader(&pktBuf, &lr)
	written, err := aeadProcess(aead, nonce, ad[:], true, mr, out)
	if err != nil {
		return nil, err
	}
	if written != fullSize {
		return nil, io.ErrUnexpectedEOF
	}
	if padSize > 0 {
		blake3.DeriveKey(key, string(MagicNNCPEv5.B[:])+" PAD", sharedKey[:])
		xof := blake3.New(32, key).XOF()
		if _, err = io.CopyN(out, xof, padSize); err != nil {
			return nil, err
		}
	}
	return pktEncRaw, nil
}

func TbsPrepare(our *NodeOur, their *Node, pktEnc *PktEnc) []byte {
	tbs := PktTbs{
		Magic:     MagicNNCPEv5.B,
		Nice:      pktEnc.Nice,
		Sender:    their.Id,
		Recipient: our.Id,
		ExchPub:   pktEnc.ExchPub,
	}
	var tbsBuf bytes.Buffer
	if _, err := xdr.Marshal(&tbsBuf, &tbs); err != nil {
		panic(err)
	}
	return tbsBuf.Bytes()
}

func TbsVerify(our *NodeOur, their *Node, pktEnc *PktEnc) ([]byte, bool, error) {
	tbs := TbsPrepare(our, their, pktEnc)
	return tbs, ed25519.Verify(their.SignPub, tbs, pktEnc.Sign[:]), nil
}

func PktEncRead(
	our *NodeOur,
	nodes map[NodeId]*Node,
	data io.Reader,
	out io.Writer,
	signatureVerify bool,
	sharedKeyCached []byte,
) ([]byte, *Node, int64, error) {
	var pktEnc PktEnc
	_, err := xdr.Unmarshal(data, &pktEnc)
	if err != nil {
		return nil, nil, 0, err
	}
	switch pktEnc.Magic {
	case MagicNNCPEv1.B:
		err = MagicNNCPEv1.TooOld()
	case MagicNNCPEv2.B:
		err = MagicNNCPEv2.TooOld()
	case MagicNNCPEv3.B:
		err = MagicNNCPEv3.TooOld()
	case MagicNNCPEv4.B:
		err = MagicNNCPEv4.TooOld()
	case MagicNNCPEv5.B:
	default:
		err = BadMagic
	}
	if err != nil {
		return nil, nil, 0, err
	}
	if *pktEnc.Recipient != *our.Id {
		return nil, nil, 0, errors.New("Invalid recipient")
	}
	var tbsRaw []byte
	var their *Node
	if signatureVerify {
		their = nodes[*pktEnc.Sender]
		if their == nil {
			return nil, nil, 0, errors.New("Unknown sender")
		}
		var verified bool
		tbsRaw, verified, err = TbsVerify(our, their, &pktEnc)
		if err != nil {
			return nil, nil, 0, err
		}
		if !verified {
			return nil, their, 0, errors.New("Invalid signature")
		}
	} else {
		tbsRaw = TbsPrepare(our, &Node{Id: pktEnc.Sender}, &pktEnc)
	}
	ad := blake3.Sum256(tbsRaw)
	sharedKey := new([32]byte)
	if sharedKeyCached == nil {
		curve25519.ScalarMult(sharedKey, our.ExchPrv, &pktEnc.ExchPub)
	} else {
		copy(sharedKey[:], sharedKeyCached)
	}

	key := make([]byte, chacha20poly1305.KeySize)
	blake3.DeriveKey(key, string(MagicNNCPEv5.B[:]), sharedKey[:])
	aead, err := chacha20poly1305.New(key)
	if err != nil {
		return sharedKey[:], their, 0, err
	}
	nonce := make([]byte, aead.NonceSize())

	sizeBuf := make([]byte, 8+aead.Overhead())
	if _, err = io.ReadFull(data, sizeBuf); err != nil {
		return sharedKey[:], their, 0, err
	}
	sizeBuf, err = aead.Open(sizeBuf[:0], nonce, sizeBuf, ad[:])
	if err != nil {
		return sharedKey[:], their, 0, err
	}
	size := int64(binary.BigEndian.Uint64(sizeBuf))

        fmt.Printf("pktEncRead: Making LimitedReader, size: %d\n", size);
	lr := io.LimitedReader{R: data, N: size}
	written, err := aeadProcess(aead, nonce, ad[:], false, &lr, out)
	if err != nil {
		return sharedKey[:], their, int64(written), err
	}
	if written != int(size) {
		return sharedKey[:], their, int64(written), io.ErrUnexpectedEOF
	}
	return sharedKey[:], their, size, nil
}

[-- Attachment #3: Type: text/plain, Size: 2179 bytes --]



On Fri, Jul 09 2021, John Goerzen wrote:

> I'm fairly baffled by this.  I ran it under strace, and there's 
> no syscall
> that's returning EOF.  nncp-pkt is easier to analyze under 
> strace, and it does a
> 4096-byte read, gets a 4096-byte result, then crashes with 
> unexpected EOF.
>
> I don't know Go, though...  I'm also baffled at how the test at 
> pkt.go line 163
> could cause an error.
>
> - John
>
> On Fri, Jul 09 2021, John Goerzen wrote:
>
>> Hello,
>>
>> I have a 3GB packet that was transferred from its origin to my 
>> gateway machine
>> via nncp-call.  When called on it, nncp-check works fine and 
>> verifies its
>> integrity.  However:
>>
>> nncp@nncp:~$ nncp-toss -progress
>> 2021-07-09T15:10:27Z ERROR Tossing
>> mccoy/74HUFHYZOB7J7MLU7SHKTRODN7OH7HRLSSTKWEEDKNMXLOJBDI2Q:
>> unmarshal: xdr:DecodeFixedOpaque: unexpected EOF while decoding 
>> 8 bytes -
>> read:
>> '[]'
>>
>> And the log shows:
>>
>> When: 2021-07-09T15:12:13.896311727Z
>> Err: xdr:DecodeFixedOpaque: unexpected EOF while decoding 8 
>> bytes - read: '[]'
>> Who: rx-unmarshal
>> Node: NE2CD32WK62N3QWWQLJUZXVX2WZAIMM7DBIXQVJTFDSXHAWSULBQ
>> Pkt: 74HUFHYZOB7J7MLU7SHKTRODN7OH7HRLSSTKWEEDKNMXLOJBDI2Q
>> Nice: 226
>> Msg: Tossing 
>> mccoy/74HUFHYZOB7J7MLU7SHKTRODN7OH7HRLSSTKWEEDKNMXLOJBDI2Q:
>> unmarshal
>>
>> (this is a transitional packet)
>>
>> Also:
>>
>> nncp@nncp:~$ nncp-pkt -dump <
>> /var/spool/nncp/NE2CD32WK62N3QWWQLJUZXVX2WZAIMM7DBIXQVJTFDSXHAWSULBQ/rx/74HUFHYZOB7J7MLU7SHKTRODN7OH7HRLSSTKWEEDKNMXLOJBDI2Q
>> main.go:163: unexpected EOF
>>
>> These commands that return with "unexpected EOF" are returning 
>> almost
>> immediately.  They could not possibly have processed 3GB like 
>> nncp-check does.
>> If I omit -dump from nncp-pkt, it works fine.  I tried 
>> reblocking with dd, and
>> that didn't help either.
>>
>> This occurs both with go 1.14 and go 1.15.
>>
>> The only unique thing here is that it's from a new node on my 
>> network.
>>
>> Other packets from that node were processed successfully, 
>> though they were
>> much
>> smaller.  I tried removing the .hdr file and that didn't help 
>> either (it was
>> recreated).
>>
>> Thanks!
>>
>> - John

  reply	other threads:[~2021-07-09 20:24 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-07-09 15:23 Unexpected EOF in 7.2.0 John Goerzen
2021-07-09 16:42 ` John Goerzen
2021-07-09 20:23   ` John Goerzen [this message]
2021-07-10  7:38     ` Sergey Matveev
2021-07-10 13:04       ` John Goerzen
2021-07-10 13:38         ` Sergey Matveev
2021-07-10 22:38           ` John Goerzen