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

Excessive memory allocation due to arbitrary offsets in Encapsulated ICAP header #5

Open
k00l-beanz opened this issue Jan 14, 2025 · 0 comments

Comments

@k00l-beanz
Copy link

Hallo 🍰,

Great library. Saved me a lot of time on a recent project 😄!

Description

During implementation, I discovered a bug causing excessive resource consumption. A client can send an ICAP request with an arbitrary offset set in either the req-hdr or res-hdr Encapsulated header values. This arbitrary offset causes the application to allocate n bytes of memory, where n is the value specified in the req-hdr and/or res-hdr.

Evidence

The bug occurs in the ReadRequest function, which is responsible for parsing the ICAP request.

When the function encounters the Encapsulated header, it reads the various values supplied in the header. The first part of the issue arises during the initial iteration of the loop. Since prevKey is empty, initialOffset is set to the value extracted from the first entry in the Encapsulated header:

icap/request.go

Lines 85 to 114 in ca4fad4

for _, item := range eList {
eq := strings.Index(item, "=")
if eq == -1 {
return nil, &badStringError{"malformed Encapsulated: header", s}
}
key := item[:eq]
value, err := strconv.Atoi(item[eq+1:])
if err != nil {
return nil, &badStringError{"malformed Encapsulated: header", s}
}
// Calculate the length of the previous section.
switch prevKey {
case "":
initialOffset = value
case "req-hdr":
reqHdrLen = value - prevValue
case "res-hdr":
respHdrLen = value - prevValue
case "req-body", "opt-body", "res-body", "null-body":
return nil, fmt.Errorf("%s must be the last section", prevKey)
}
switch key {
case "req-hdr", "res-hdr", "null-body":
case "req-body", "res-body", "opt-body":
hasBody = true
default:
return nil, &badStringError{"invalid key for Encapsulated: header", key}
}

The second part of the issue occurs when the library attempts to position the cursor at the start of the Encapsulated HTTP header by reading initialOffset bytes. During this process, a byte slice named junk is initialized to read up to the beginning of the Encapsulated HTTP header. Since initialOffset is arbitrary, the make call can allocate an arbitrary amount of memory. Both reqHdrLen and respHdrLen are also arbitrary and are affected by the same logic. This can lead to excessive resource consumption and result in a Denial-of-Service.

icap/request.go

Lines 121 to 142 in ca4fad4

var rawReqHdr, rawRespHdr []byte
if initialOffset > 0 {
junk := make([]byte, initialOffset)
_, err = io.ReadFull(b, junk)
if err != nil {
return nil, err
}
}
if reqHdrLen > 0 {
rawReqHdr = make([]byte, reqHdrLen)
_, err = io.ReadFull(b, rawReqHdr)
if err != nil {
return nil, err
}
}
if respHdrLen > 0 {
rawRespHdr = make([]byte, respHdrLen)
_, err = io.ReadFull(b, rawRespHdr)
if err != nil {
return nil, err
}
}

Below is a small PoC. I've also attached the payload along with this Issue

package main

import (
	"bufio"
	"log"
	"os"

	"github.com/jrossi/go-icap"
)

func main() {
	f, err := os.Open("./icap-crash-0.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	reader := bufio.NewReader(f)
	writer := bufio.NewWriter(f)

	readWriter := bufio.NewReadWriter(reader, writer)
	icap.ReadRequest(readWriter)
}

icap-crash-0.txt

Run and build:

$ go build .
$ ./main
runtime: out of memory: cannot allocate 203043719086080-byte block (3866624 in use)
fatal error: out of memory

goroutine 1 gp=0xc0000061c0 m=0 mp=0x6fdaa0 [running]:
runtime.throw({0x59944c?, 0xffffffffffffffff?})
        /usr/local/go/src/runtime/panic.go:1067 +0x48 fp=0xc000079af0 sp=0xc000079ac0 pc=0x46b3a8
runtime.(*mcache).allocLarge(0xc000079b68?, 0xb8aacc8d991e, 0x1)
        /usr/local/go/src/runtime/mcache.go:236 +0x18b fp=0xc000079b40 sp=0xc000079af0 pc=0x413f6b
runtime.mallocgc(0xb8aacc8d991e, 0x55f060, 0x1)
        /usr/local/go/src/runtime/malloc.go:1177 +0x5d0 fp=0xc000079be0 sp=0xc000079b40 pc=0x466b70
runtime.makeslice(0xc00001a1b8?, 0xf?, 0x5ea5f0?)
        /usr/local/go/src/runtime/slice.go:116 +0x49 fp=0xc000079c08 sp=0xc000079be0 pc=0x46cda9
github.com/jrossi/go-icap.ReadRequest(0xc000016100)
        /home/user/go/pkg/mod/github.com/jrossi/go-icap@v0.0.0-20151011115316-ca4fad4ebb28/request.go:123 +0x855 fp=0xc000079e78 sp=0xc000079c08 pc=0x54b3f5
main.main()
        /home/user/Documents/research/go-icap/main.go:22 +0x252 fp=0xc000079f50 sp=0xc000079e78 pc=0x54c672
runtime.main()
        /usr/local/go/src/runtime/proc.go:272 +0x28b fp=0xc000079fe0 sp=0xc000079f50 pc=0x4386cb
runtime.goexit({})
        /usr/local/go/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc000079fe8 sp=0xc000079fe0 pc=0x4727c1

goroutine 2 gp=0xc000006c40 m=nil [force gc (idle)]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
        /usr/local/go/src/runtime/proc.go:424 +0xce fp=0xc000062fa8 sp=0xc000062f88 pc=0x46b4ce
runtime.goparkunlock(...)
        /usr/local/go/src/runtime/proc.go:430
runtime.forcegchelper()
        /usr/local/go/src/runtime/proc.go:337 +0xb3 fp=0xc000062fe0 sp=0xc000062fa8 pc=0x438a13
runtime.goexit({})
        /usr/local/go/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc000062fe8 sp=0xc000062fe0 pc=0x4727c1
created by runtime.init.7 in goroutine 1
        /usr/local/go/src/runtime/proc.go:325 +0x1a

goroutine 3 gp=0xc000007180 m=nil [GC sweep wait]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
        /usr/local/go/src/runtime/proc.go:424 +0xce fp=0xc000063780 sp=0xc000063760 pc=0x46b4ce
runtime.goparkunlock(...)
        /usr/local/go/src/runtime/proc.go:430
runtime.bgsweep(0xc000090000)
        /usr/local/go/src/runtime/mgcsweep.go:277 +0x94 fp=0xc0000637c8 sp=0xc000063780 pc=0x423694
runtime.gcenable.gowrap1()
        /usr/local/go/src/runtime/mgc.go:204 +0x25 fp=0xc0000637e0 sp=0xc0000637c8 pc=0x417fa5
runtime.goexit({})
        /usr/local/go/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc0000637e8 sp=0xc0000637e0 pc=0x4727c1
created by runtime.gcenable in goroutine 1
        /usr/local/go/src/runtime/mgc.go:204 +0x66

goroutine 4 gp=0xc000007340 m=nil [GC scavenge wait]:
runtime.gopark(0xc000090000?, 0x5ea558?, 0x1?, 0x0?, 0xc000007340?)
        /usr/local/go/src/runtime/proc.go:424 +0xce fp=0xc000063f78 sp=0xc000063f58 pc=0x46b4ce
runtime.goparkunlock(...)
        /usr/local/go/src/runtime/proc.go:430
runtime.(*scavengerState).park(0x6fcce0)
        /usr/local/go/src/runtime/mgcscavenge.go:425 +0x49 fp=0xc000063fa8 sp=0xc000063f78 pc=0x4210c9
runtime.bgscavenge(0xc000090000)
        /usr/local/go/src/runtime/mgcscavenge.go:653 +0x3c fp=0xc000063fc8 sp=0xc000063fa8 pc=0x42163c
runtime.gcenable.gowrap2()
        /usr/local/go/src/runtime/mgc.go:205 +0x25 fp=0xc000063fe0 sp=0xc000063fc8 pc=0x417f45
runtime.goexit({})
        /usr/local/go/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc000063fe8 sp=0xc000063fe0 pc=0x4727c1
created by runtime.gcenable in goroutine 1
        /usr/local/go/src/runtime/mgc.go:205 +0xa5

goroutine 5 gp=0xc000007c00 m=nil [finalizer wait]:
runtime.gopark(0xc000062648?, 0x40ea85?, 0xb0?, 0x1?, 0xc0000061c0?)
        /usr/local/go/src/runtime/proc.go:424 +0xce fp=0xc000062620 sp=0xc000062600 pc=0x46b4ce
runtime.runfinq()
        /usr/local/go/src/runtime/mfinal.go:193 +0x107 fp=0xc0000627e0 sp=0xc000062620 pc=0x417027
runtime.goexit({})
        /usr/local/go/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc0000627e8 sp=0xc0000627e0 pc=0x4727c1
created by runtime.createfing in goroutine 1
        /usr/local/go/src/runtime/mfinal.go:163 +0x3d

goroutine 6 gp=0xc000007dc0 m=nil [runnable]:
runtime.unique_runtime_registerUniqueMapCleanup.gowrap1()
        /usr/local/go/src/runtime/mgc.go:1779 fp=0xc0000647e0 sp=0xc0000647d8 pc=0x41ada0
runtime.goexit({})
        /usr/local/go/src/runtime/asm_amd64.s:1700 +0x1 fp=0xc0000647e8 sp=0xc0000647e0 pc=0x4727c1
created by unique.runtime_registerUniqueMapCleanup in goroutine 1
        /usr/local/go/src/runtime/mgc.go:1779 +0x96

Cheers mate 🍻,
~ k00l-beanz

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant