diff --git a/v3/bind.go b/v3/bind.go index 7e5ae5ab..b0d810b7 100644 --- a/v3/bind.go +++ b/v3/bind.go @@ -293,11 +293,10 @@ func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*Diges return result, GetLDAPError(packet) } if ber.Type(resultCodeChild.Tag) == ber.Type(ber.TagInteger) || ber.Type(resultCodeChild.Tag) == ber.Type(ber.TagEnumerated) { - code, rerr := packetInt64(resultCodeChild) + resultCode, rerr := packetResultCode(resultCodeChild) if rerr != nil { return result, GetLDAPError(packet) } - resultCode := uint16(code) if resultCode == 14 { msgCtx, err := l.doRequest(digestMD5BindRequest) if err != nil { diff --git a/v3/error.go b/v3/error.go index 2f839bfd..58149506 100644 --- a/v3/error.go +++ b/v3/error.go @@ -218,11 +218,10 @@ func parseLDAPResult(packet *ber.Packet) (resultCode uint16, matchedDN, diagnost return 0, "", "", err } - v, err := packetInt64(children[0]) + resultCode, err = packetResultCode(children[0]) if err != nil { return } - resultCode = uint16(v) if matchedDN, err = packetString(children[1]); err != nil { return @@ -251,12 +250,11 @@ func GetLDAPError(packet *ber.Packet) error { return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("invalid result code in packet"), Packet: packet} } if ber.Type(resultCodeChild.Tag) == ber.Type(ber.TagInteger) || ber.Type(resultCodeChild.Tag) == ber.Type(ber.TagEnumerated) { - code, err := packetInt64(resultCodeChild) + resultCode, err := packetResultCode(resultCodeChild) if err != nil { return &Error{ResultCode: ErrorNetwork, Err: fmt.Errorf("invalid result code in packet"), Packet: packet} } - resultCode := uint16(code) if resultCode == 0 { // No error return nil } diff --git a/v3/error_test.go b/v3/error_test.go index 3b7e9931..8244b631 100644 --- a/v3/error_test.go +++ b/v3/error_test.go @@ -155,6 +155,24 @@ func generateGetLDAPErrorCorpus() map[string]testCorpusErrorEntry { expectedMessage: "", } + // A resultCode outside the uint16 range the package exposes must be + // rejected. Narrowing it maps any multiple of 2^16 onto success(0), so a + // reply carrying no success code would be reported to the caller as a + // successful operation. + bindResponse = ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") + bindResponse.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, int64(65536), "resultCode")) + bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "matchedDN")) + bindResponse.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "diagnosticMessage")) + packet = ber.NewSequence("LDAPMessage") + packet.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(0), "messageID")) + packet.AppendChild(bindResponse) + corpus["result code out of range"] = testCorpusErrorEntry{ + packet: packet, + expectedResultCode: ErrorNetwork, + expectedMessage: "invalid result code in packet", + shouldError: true, + } + // Test that responses with an unexpected ordering or combination of children // don't cause a panic. bindResponse = ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindResponse, nil, "Bind Response") @@ -420,6 +438,20 @@ func TestParseLDAPResult(t *testing.T) { }(), wantErr: true, }, + { + name: "resultCode out of range", + packet: func() *ber.Packet { + resp := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchResultDone, nil, "SearchResultDone") + resp.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, int64(65536), "resultCode")) + resp.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "matchedDN")) + resp.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "diagnosticMessage")) + pkt := ber.NewSequence("LDAPMessage") + pkt.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(1), "messageID")) + pkt.AppendChild(resp) + return pkt + }(), + wantErr: true, + }, { name: "matchedDN wrong type", packet: func() *ber.Packet { diff --git a/v3/packet.go b/v3/packet.go index 273307d6..64500a51 100644 --- a/v3/packet.go +++ b/v3/packet.go @@ -3,6 +3,7 @@ package ldap import ( "errors" "fmt" + "math" ber "github.com/go-asn1-ber/asn1-ber" ) @@ -107,6 +108,22 @@ func packetInt64(p *ber.Packet) (int64, error) { return i, nil } +// packetResultCode returns the LDAPResult resultCode held by p. RFC 4511 +// declares resultCode an ENUMERATED and the codes it assigns all fit the uint16 +// this package exposes, so a value outside that range is malformed rather than +// merely unknown. Narrowing it instead would alias it onto an unrelated code, +// and any multiple of 2^16 onto success(0). +func packetResultCode(p *ber.Packet) (uint16, error) { + code, err := packetInt64(p) + if err != nil { + return 0, err + } + if code < 0 || code > math.MaxUint16 { + return 0, malformedf("result code %d out of range", code) + } + return uint16(code), nil +} + // packetBool returns the bool value of p, whose schema position or BOOLEAN tag // fixed it as a boolean. func packetBool(p *ber.Packet) (bool, error) { diff --git a/v3/packet_test.go b/v3/packet_test.go index bd1d6f2b..4b782d2c 100644 --- a/v3/packet_test.go +++ b/v3/packet_test.go @@ -109,6 +109,18 @@ func TestPacketLeafConversions(t *testing.T) { if b, err := packetBool(newTestBool(true)); err != nil || !b { t.Fatalf("packetBool: got %v, %v", b, err) } + if c, err := packetResultCode(newTestInt(49)); err != nil || c != 49 { + t.Fatalf("packetResultCode: got %d, %v", c, err) + } + + // A resultCode outside the uint16 range is malformed, not merely unknown: + // narrowing it aliases it onto an unrelated code, and any multiple of 2^16 + // onto success(0). + for _, code := range []int64{-1, -65536, 65536, 131072} { + if _, err := packetResultCode(newTestInt(code)); !errors.Is(err, ErrMalformedPacket) { + t.Fatalf("packetResultCode(%d): expected ErrMalformedPacket, got %v", code, err) + } + } // A field whose Value has the wrong Go type must produce a // malformed-packet error, never a panic.