detached 648b1ef

fix(core): reject video sizes whose frame does not fit an int32

NewVideoSource and Resize accepted 32768x16384 and larger frames, whose byte size wraps the SPA buffer size and the data maxsize.
commit
648b1efadd05a6c40913c65f91230823fe2991c5
author
brice · · 2026-10-14 21:04 UTC
parent
f9ca745
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +12 -2
M internal/core/video.go +9 -2
@@ -4,6 +4,7 @@ import (
44 "errors"
55 "fmt"
66 "log/slog"
7+ "math"
78 "strings"
89 "sync"
910 "sync/atomic"
@@ -213,7 +214,7 @@ func (c *Core) NewVideoSource(cfg VideoConfig, h portin.VideoHandler) (*VideoSou
213214 if c.ptr == nil || c.capi == nil {
214215 return nil, ErrVideoStreamCreate
215216 }
216- if h == nil || cfg.Width <= 0 || cfg.Height <= 0 || cfg.Width > 1<<15 || cfg.Height > 1<<15 {
217+ if h == nil || !validVideoSize(cfg.Width, cfg.Height) {
217218 return nil, ErrVideoConfig
218219 }
219220 if cfg.MaxFPS <= 0 {
@@ -387,6 +388,12 @@ func (s *VideoSource) Trigger() {
387388 }
388389 }
389390
391+// validVideoSize reports whether a w×h frame of 4-byte pixels fits the
392+// int32 sizes SPA buffers carry.
393+func validVideoSize(w, h int) bool {
394+ return w > 0 && h > 0 && w <= 1<<15 && h <= 1<<15 && w*4*h <= math.MaxInt32
395+}
396+
390397 // Resize renegotiates the stream with a new size. Buffers PipeWire replaces
391398 // stay valid while borrowed; hand them back with Queue or Release (ideally
392399 // resize when none is borrowed).
@@ -394,7 +401,7 @@ func (s *VideoSource) Resize(w, h int) error {
394401 if s.stream == nil {
395402 return ErrVideoResize
396403 }
397- if w <= 0 || h <= 0 || w > 1<<15 || h > 1<<15 {
404+ if !validVideoSize(w, h) {
398405 return ErrVideoConfig
399406 }
400407 if _, err := spa.VideoEnumFormat(s.enumPod[:], w, h, s.cfg.MaxFPS, s.formats[:s.nformats]...); err != nil {
@@ -4,6 +4,7 @@ import (
4 "errors"4 "errors"
5 "fmt"5 "fmt"
6 "log/slog"6 "log/slog"
7+ "math"
7 "strings"8 "strings"
8 "sync"9 "sync"
9 "sync/atomic"10 "sync/atomic"
@@ -213,7 +214,7 @@ func (c *Core) NewVideoSource(cfg VideoConfig, h portin.VideoHandler) (*VideoSou
213 if c.ptr == nil || c.capi == nil {214 if c.ptr == nil || c.capi == nil {
214 return nil, ErrVideoStreamCreate215 return nil, ErrVideoStreamCreate
215 }216 }
216- if h == nil || cfg.Width <= 0 || cfg.Height <= 0 || cfg.Width > 1<<15 || cfg.Height > 1<<15 {217+ if h == nil || !validVideoSize(cfg.Width, cfg.Height) {
217 return nil, ErrVideoConfig218 return nil, ErrVideoConfig
218 }219 }
219 if cfg.MaxFPS <= 0 {220 if cfg.MaxFPS <= 0 {
@@ -387,6 +388,12 @@ func (s *VideoSource) Trigger() {
387 }388 }
388 }389 }
389 390
391+// validVideoSize reports whether a w×h frame of 4-byte pixels fits the
392+// int32 sizes SPA buffers carry.
393+func validVideoSize(w, h int) bool {
394+ return w > 0 && h > 0 && w <= 1<<15 && h <= 1<<15 && w*4*h <= math.MaxInt32
395+}
396+
390 // Resize renegotiates the stream with a new size. Buffers PipeWire replaces397 // Resize renegotiates the stream with a new size. Buffers PipeWire replaces
391 // stay valid while borrowed; hand them back with Queue or Release (ideally398 // stay valid while borrowed; hand them back with Queue or Release (ideally
392 // resize when none is borrowed).399 // resize when none is borrowed).
@@ -394,7 +401,7 @@ func (s *VideoSource) Resize(w, h int) error {
394 if s.stream == nil {401 if s.stream == nil {
395 return ErrVideoResize402 return ErrVideoResize
396 }403 }
397- if w <= 0 || h <= 0 || w > 1<<15 || h > 1<<15 {404+ if !validVideoSize(w, h) {
398 return ErrVideoConfig405 return ErrVideoConfig
399 }406 }
400 if _, err := spa.VideoEnumFormat(s.enumPod[:], w, h, s.cfg.MaxFPS, s.formats[:s.nformats]...); err != nil {407 if _, err := spa.VideoEnumFormat(s.enumPod[:], w, h, s.cfg.MaxFPS, s.formats[:s.nformats]...); err != nil {
M internal/core/video_test.go +3 -0
@@ -447,6 +447,9 @@ func TestNewVideoSourceErrors(t *testing.T) {
447447 if _, err := core.NewVideoSource(VideoConfig{Width: 0, Height: 10}, h); !errors.Is(err, ErrVideoConfig) {
448448 t.Errorf("zero width: %v", err)
449449 }
450+ if _, err := core.NewVideoSource(VideoConfig{Width: 1 << 15, Height: 1 << 14}, h); !errors.Is(err, ErrVideoConfig) {
451+ t.Errorf("frame size over int32: %v", err)
452+ }
450453 if _, err := core.NewVideoSource(VideoConfig{Width: 10, Height: 10}, nil); !errors.Is(err, ErrVideoConfig) {
451454 t.Errorf("nil handler: %v", err)
452455 }
@@ -447,6 +447,9 @@ func TestNewVideoSourceErrors(t *testing.T) {
447 if _, err := core.NewVideoSource(VideoConfig{Width: 0, Height: 10}, h); !errors.Is(err, ErrVideoConfig) {447 if _, err := core.NewVideoSource(VideoConfig{Width: 0, Height: 10}, h); !errors.Is(err, ErrVideoConfig) {
448 t.Errorf("zero width: %v", err)448 t.Errorf("zero width: %v", err)
449 }449 }
450+ if _, err := core.NewVideoSource(VideoConfig{Width: 1 << 15, Height: 1 << 14}, h); !errors.Is(err, ErrVideoConfig) {
451+ t.Errorf("frame size over int32: %v", err)
452+ }
450 if _, err := core.NewVideoSource(VideoConfig{Width: 10, Height: 10}, nil); !errors.Is(err, ErrVideoConfig) {453 if _, err := core.NewVideoSource(VideoConfig{Width: 10, Height: 10}, nil); !errors.Is(err, ErrVideoConfig) {
451 t.Errorf("nil handler: %v", err)454 t.Errorf("nil handler: %v", err)
452 }455 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close