tiktak: защита от decompression bomb в /create
Приходят 100 КБ webm с VP8-кадром гигантского разрешения (boundary у них буквально ----bomb*). Проверку f.Size > 5MB он проходит, validateWebm смотрит только первые 512 байт, а ffmpeg разворачивает такой кадр в ~1 ГБ сырых пикселей. При семафоре на 75 параллельных это укладывает box, чекер не укладывается в свои 7 секунд => MUMBLE. - video/probe.go: ffprobe читает geometry из заголовка (кадр не декодируется), режем > 4096 по стороне и > 8 Mpx. - Blur: image.DecodeConfig до image.Decode, тот же лимит по пикселям. imaging.Blur держит несколько копий битмапа, без лимита это OOM сам по себе. Заодно f.Close(), которого не было. - ffmpeg -threads 1, семафор 75 -> 16. - BodyLimit 8M: размер видео проверялся, а форма нет. Огромные субтитры превращались в слайс на миллионы строк в GenerateVtt. Плюс явный MaxSubtitlesSize 64K. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/disintegration/imaging"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -14,11 +15,17 @@ import (
|
||||
|
||||
var (
|
||||
ProcessingDeadline = 2 * time.Second
|
||||
semaphore = make(chan struct{}, 75)
|
||||
// 75 concurrent ffmpeg processes are enough to swap the box out on their
|
||||
// own, bomb or no bomb.
|
||||
semaphore = make(chan struct{}, 16)
|
||||
)
|
||||
|
||||
func generatePreview(ctx context.Context, inp string, out string) error {
|
||||
cmd := exec.CommandContext(ctx, "ffmpeg", "-y", "-i", inp, "-vframes", "1", out)
|
||||
// Reject decompression bombs before ffmpeg expands a frame into memory.
|
||||
if err := checkGeometry(ctx, inp); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "ffmpeg", "-y", "-threads", "1", "-i", inp, "-vframes", "1", out)
|
||||
s := strings.Builder{}
|
||||
cmd.Stdout = &s
|
||||
cmd.Stderr = &s
|
||||
@@ -48,6 +55,21 @@ func Blur(inp string, out string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Same guard on the decode side: imaging.Blur keeps several copies of the
|
||||
// bitmap alive, so an oversized preview is an OOM all by itself.
|
||||
cfg, _, err := image.DecodeConfig(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.Width*cfg.Height > MaxPixels {
|
||||
return fmt.Errorf("preview %dx%d is too large to blur", cfg.Width, cfg.Height)
|
||||
}
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
img, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user