iineva / bom

Apple iOS / macOS Assets.car decoder, write in golang
12 stars 10 forks source link

Hang and OOM on some Assets.car files #2

Open MarSoft opened 1 year ago

MarSoft commented 1 year ago

When I try to use github.com/iineva/bom/pkg/asset to load App Icon from Assets.car file of the following ipa: https://github.com/StreamerApp/Streamer/releases/download/1.2.24/Streamer-tvOS.ipa it first finds the icon and then hangs and eats all the memory. Here is the example code to reproduce the issue:

package main

import (
    "fmt"
    "image"
    "os"
    "github.com/iineva/bom/pkg/asset"
)

func main() {
    f, err := os.Open("Payload/Streamer.app/Assets.car")
    defer f.Close()

    ass, err := asset.NewWithReadSeeker(f)
    if err != nil {
        panic(err)
    }
    fmt.Println("Scanning images")
    err = ass.ImageWalker(func(name string, img image.Image) bool {
        fmt.Println("Got img", name)
        return true
    })
    fmt.Println("Img res", err)
}

Actual output:

Scanning images
2023/09/27 02:27:44 TODO: handle DATA 
Got img App Icon
fatal error: runtime: out of memory
<snip>
goroutine 1 [runnable]:
runtime.goschedguarded(...)
        /.../go-1.20.7/share/go/src/runtime/proc.go:329
runtime.memclrNoHeapPointersChunked(0x1b7500000?, 0xc000600000?)
        /.../go-1.20.7/share/go/src/runtime/malloc.go:1239 +0x6e fp=0xc0000db890 sp=0xc0000db860 pc=0x40f9ce
runtime.mallocgc(0x1b7500000, 0x4bb640, 0x1)
        /.../go-1.20.7/share/go/src/runtime/malloc.go:1150 +0x6ea fp=0xc0000db8f8 sp=0xc0000db890 pc=0x40f74a
runtime.makeslice(0xb908?, 0xc000204000?, 0x36ea?)
        /.../go-1.20.7/share/go/src/runtime/slice.go:103 +0x52 fp=0xc0000db920 sp=0xc0000db8f8 pc=0x44a2f2
github.com/iineva/go-lzfse.DecodeBuffer({0xc000204000?, 0x36ea, 0x4c6480?})
        /home/me/go/pkg/mod/github.com/iineva/go-lzfse@v1.1.13-0.20210604101847-2a555776c20a/lzfse.go:61 +0x116 fp=0xc0000db9c0 sp=0xc0000db920 pc=0x4a7b16
github.com/iineva/bom/pkg/asset.umCompression(0x4f5f28?, {0x4f5e88?, 0xc0000ba5a0?})
        /home/me/go/pkg/mod/github.com/iineva/bom@v0.0.0-20210605043415-7d45ba1bcca3/pkg/asset/decode_rgb.go:130 +0x65 fp=0xc0000dba48 sp=0xc0000db9c0 pc=0x4a9aa5
github.com/iineva/bom/pkg/asset.(*asset).decodeImage(0xc0000dbbb0?, {0xc000021490, 0x4}, {0x4f5f28, 0xc0000ba4e0}, 0xc00002e600)
        /home/me/go/pkg/mod/github.com/iineva/bom@v0.0.0-20210605043415-7d45ba1bcca3/pkg/asset/decode_rgb.go:106 +0x6b3 fp=0xc0000dbb30 sp=0xc0000dba48 pc=0x4a9713
github.com/iineva/bom/pkg/asset.(*asset).Renditions.func1({0x4f5f28, 0xc0000ba480}, {0x4f5f28?, 0xc0000ba4e0?})
        /home/me/go/pkg/mod/github.com/iineva/bom@v0.0.0-20210605043415-7d45ba1bcca3/pkg/asset/decode.go:246 +0x47b fp=0xc0000dbcb0 sp=0xc0000dbb30 pc=0x4a8a7b
github.com/iineva/bom/pkg/bom.(*bom).ReadTree(0xc0000a4040, {0x4d4af3, 0xa}, 0xc000016180)
        /home/me/go/pkg/mod/github.com/iineva/bom@v0.0.0-20210605043415-7d45ba1bcca3/pkg/bom/decode.go:199 +0x699 fp=0xc0000dbdb0 sp=0xc0000dbcb0 pc=0x4a0f99
github.com/iineva/bom/pkg/asset.(*asset).Renditions(0xc000092460, 0xc0000120d8)
        /home/me/go/pkg/mod/github.com/iineva/bom@v0.0.0-20210605043415-7d45ba1bcca3/pkg/asset/decode.go:209 +0xc4 fp=0xc0000dbde8 sp=0xc0000dbdb0 pc=0x4a85a4
github.com/iineva/bom/pkg/asset.(*asset).ImageWalker(0x10?, 0x4dd168)
        /home/me/go/pkg/mod/github.com/iineva/bom@v0.0.0-20210605043415-7d45ba1bcca3/pkg/asset/decode.go:300 +0x1f2 fp=0xc0000dbeb0 sp=0xc0000dbde8 pc=0x4a8f12
main.main.func1(0xc0000dbf18, 0xc000014018?)
        /tmp/streamer/test.go:27 +0x4e fp=0xc0000dbee0 sp=0xc0000dbeb0 pc=0x4aa8ae
main.main()
        /tmp/streamer/test.go:31 +0x105 fp=0xc0000dbf80 sp=0xc0000dbee0 pc=0x4aa785
runtime.main()
        /.../go-1.20.7/share/go/src/runtime/proc.go:250 +0x207 fp=0xc0000dbfe0 sp=0xc0000dbf80 pc=0x437207
runtime.goexit()
        /.../go-1.20.7/share/go/src/runtime/asm_amd64.s:1598 +0x1 fp=0xc0000dbfe8 sp=0xc0000dbfe0 pc=0x462281

<snip>
MarSoft commented 1 year ago

Looks like it does not stop iterating even after true is returned which should denote Stop iterating. And here is the (dirty) workaround I currently use:

<snip>
    fmt.Println("Scanning images")
    var imgFound image.Image
    func() {
        defer func() {
            if e := recover(); e == 42 {
                fmt.Println("Got 42")
            } else if e != nil {
                panic(e)
            }
        }()
        err = ass.ImageWalker(func(name string, img image.Image) bool {
            fmt.Println("Got img", name)
            imgFound = img
            panic(42)
        })
    }()
    fmt.Println("Img res", err, imgFound)
}

A bit of explanation: when the desired image is found, my walker function saves it and panics. The outer function reccovers from that panic and returns gracefully. As a result, the hanging code is just bypassed. But this won't work if the library hangs before finding the desired image.