Repository: pkg/sftp (go)

Issue:
Request.Attributes() can panic when called with the Attr slice empty
As mentioned in the title, I got a panic in production.

Is this supposed to happen and am I supposed to check the byte slice myself prior to calling `Attributes()`, or can we just check that in the function (that would be my preference) ? Or is the byte slice always supposed to be filled? (My guess this is some weird client that did not comply to the SFTP standards).

Panic here:

```
panic: runtime error: index out of range [3] with length 0

goroutine 261287 [running]:
github.com/pkg/sftp.unmarshalUint32(...)
        /go/pkg/mod/github.com/pkg/sftp@v1.10.1/packet.go:73
github.com/pkg/sftp.getFileStat(0x138, 0xc0016125df, 0x4, 0x4, 0x3, 0x8, 0x30, 0x8)
        /go/pkg/mod/github.com/pkg/sftp@v1.10.1/attrs.go:118 +0x53d
github.com/pkg/sftp.(*Request).Attributes(...)
        /go/pkg/mod/github.com/pkg/sftp@v1.10.1/request-attrs.go:61
source-dir/sftp.(*RequestHandler).handleSetStat(0xc001324cc0, 0xc000dcd620, 0x6, 0x6, 0xc0006fe540, 0xc0011d1030, 0x6, 0x6)
        /source-dir/sftp/request_handler.go:296 +0x65
source-dir/sftp.(*RequestHandler).Filecmd(0xc001324cc0, 0xc0006fe540, 0xc00045bba0, 0x10)
        /source-dir/sftp/request_handler.go:276 +0x6e2
github.com/pkg/sftp.filecmd(0xfd6d00, 0xc001324cc0, 0xc0006fe540, 0xfe00c0, 0xc0023aba40, 0xfdf180, 0x40df9b)
        /go/pkg/mod/github.com/pkg/sftp@v1.10.1/request.go:267 +0xd4
github.com/pkg/sftp.(*Request).call(0xc0006fe540, 0xfd6d40, 0xc001324cc0, 0xfd6d60, 0xc001324cc0, 0xfd6d00, 0xc001324cc0, 0xfd6d20, 0xc001324cc0, 0xfe00c0, ...)
        /go/pkg/mod/github.com/pkg/sftp@v1.10.1/request.go:162 +0x389
github.com/pkg/sftp.(*RequestServer).packetWorker(0xc001161000, 0xfec780, 0xc000d2d180, 0xc000da4600, 0x70189a101, 0xc00082c7c8)
        /go/pkg/mod/github.com/pkg/sftp@v1.10.1/request-server.go:187 +0x559
github.com/pkg/sftp.(*RequestServer).Serve.func1.1(0xc000c07030, 0xc001161000, 0xfec780, 0xc000d2d180, 0xc000da4600)
        /go/pkg/mod/github.com/pkg/sftp@v1.10.1/request-server.go:98 +0x89
created by github.com/pkg/sftp.(*RequestServer).Serve.func1
        /go/pkg/mod/github.com/pkg/sftp@v1.10.1/request-server.go:96 +0xa1
```

Pull request:
Fix panics when attempting to get file attributes from too small byte slices
Fixes #325 

Patch:
diff --git a/attrs.go b/attrs.go
index 1a547b0..58af665 100644
--- a/attrs.go
+++ b/attrs.go
@@ -15,6 +15,9 @@ const (
 	sshFileXferAttrPermissions = 0x00000004
 	sshFileXferAttrACmodTime   = 0x00000008
 	sshFileXferAttrExtented    = 0x80000000
+
+	sshFileXferAttrAll = sshFileXferAttrSize|sshFileXferAttrUIDGID|sshFileXferAttrPermissions|
+		sshFileXferAttrACmodTime|sshFileXferAttrExtented
 )
 
 // fileInfo is an artificial type designed to satisfy os.FileInfo.
@@ -102,30 +105,30 @@ func unmarshalAttrs(b []byte) (*FileStat, []byte) {
 func getFileStat(flags uint32, b []byte) (*FileStat, []byte) {
 	var fs FileStat
 	if flags&sshFileXferAttrSize == sshFileXferAttrSize {
-		fs.Size, b = unmarshalUint64(b)
+		fs.Size, b, _ = unmarshalUint64Safe(b)
 	}
 	if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
-		fs.UID, b = unmarshalUint32(b)
+		fs.UID, b, _ = unmarshalUint32Safe(b)
 	}
 	if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
-		fs.GID, b = unmarshalUint32(b)
+		fs.GID, b, _ = unmarshalUint32Safe(b)
 	}
 	if flags&sshFileXferAttrPermissions == sshFileXferAttrPermissions {
-		fs.Mode, b = unmarshalUint32(b)
+		fs.Mode, b, _ = unmarshalUint32Safe(b)
 	}
 	if flags&sshFileXferAttrACmodTime == sshFileXferAttrACmodTime {
-		fs.Atime, b = unmarshalUint32(b)
-		fs.Mtime, b = unmarshalUint32(b)
+		fs.Atime, b, _ = unmarshalUint32Safe(b)
+		fs.Mtime, b, _ = unmarshalUint32Safe(b)
 	}
 	if flags&sshFileXferAttrExtented == sshFileXferAttrExtented {
 		var count uint32
-		count, b = unmarshalUint32(b)
+		count, b, _ = unmarshalUint32Safe(b)
 		ext := make([]StatExtended, count)
 		for i := uint32(0); i < count; i++ {
 			var typ string
 			var data string
-			typ, b = unmarshalString(b)
-			data, b = unmarshalString(b)
+			typ, b, _ = unmarshalStringSafe(b)
+			data, b, _ = unmarshalStringSafe(b)
 			ext[i] = StatExtended{typ, data}
 		}
 		fs.Extended = ext


Test patch:
diff --git a/request-attrs_test.go b/request-attrs_test.go
index a239d17..423a17c 100644
--- a/request-attrs_test.go
+++ b/request-attrs_test.go
@@ -2,10 +2,9 @@ package sftp
 
 import (
 	"os"
+	"testing"
 
 	"github.com/stretchr/testify/assert"
-
-	"testing"
 )
 
 func TestRequestPflags(t *testing.T) {
@@ -49,3 +48,11 @@ func TestRequestAttributes(t *testing.T) {
 	assert.False(t, testFs.FileMode().IsDir())
 	assert.Equal(t, testFs.FileMode().Perm(), os.FileMode(700).Perm())
 }
+
+func TestRequestAttributesEmpty(t *testing.T) {
+	fs, b := getFileStat(sshFileXferAttrAll, nil)
+	assert.Equal(t, &FileStat{
+		Extended: []StatExtended{},
+	}, fs)
+	assert.Empty(t, b)
+}