security.go RawHistoryFolder
1package drm
2
3import (
4 "context"
5 "errors"
6 "time"
7
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11var errSecurityScene = errors.New("scene security epoch changed or output unprepared")
12
13// observeSecurity is output-owner only. The channel wakes; the coherent gate
14// decides admission, including shutdown paths that never received the wake.
15func (o *Output) observeSecurity() bool {
16 if o.Security == nil {
17 return false
18 }
19 state := o.Security.Snapshot()
20 if state == o.securityState {
21 return false
22 }
23 o.securityState = state
24 o.securityPrepared = false
25 o.protected = state.Protected
26 o.protectSucceeded() // a new epoch gets an immediate first attempt
27 if o.cursor != nil {
28 o.cursor.image, o.cursor.later = false, nil
29 }
30 return true
31}
32
33func (o *Output) sceneCurrent(scene ports.Scene) bool {
34 o.observeSecurity()
35 return o.Security == nil || scene.Security == o.securityState
36}
37
38func (o *Output) invalidateSecurity() {
39 o.securityPrepared = false
40 if o.protected {
41 o.securityInvalid = true
42 }
43}
44
45func (o *Output) securityEvent(ctx context.Context, event ports.SecurityBackendEvent) error {
46 if o.SecurityEvents == nil || o.Instance == 0 {
47 return nil
48 }
49 select {
50 case o.SecurityEvents <- event:
51 return nil
52 case <-ctx.Done():
53 return ctx.Err()
54 }
55}
56
57func (o *Output) sendSecurityInvalid(ctx context.Context) error {
58 if !o.securityInvalid {
59 return nil
60 }
61 state := o.securityState
62 if err := o.securityEvent(ctx, ports.SecurityOutputInvalidated{Generation: state.Generation, Instance: o.Instance}); err != nil {
63 return err
64 }
65 o.securityInvalid = false
66 return nil
67}
68
69func (o *Output) prepareSecurity(ctx context.Context, r ports.Renderer) error {
70 o.observeSecurity()
71 if !o.protected {
72 return nil
73 }
74 if err := o.sendSecurityInvalid(ctx); err != nil {
75 return err
76 }
77 state := o.securityState
78 if err := o.protectedModeset(ctx, r); err != nil {
79 return err
80 }
81 // A gate transition during the blocking clear cannot certify a new epoch.
82 if o.observeSecurity() || o.securityState != state {
83 return nil
84 }
85 o.securityPrepared = true
86 kind := ports.ProtectionProtectedFrame
87 if o.off {
88 kind = ports.ProtectionInactiveOutput
89 }
90 // Separate non-merging channel; presentation feedback is not evidence.
91 // Backpressure may outlive an epoch. Revalidate on each bounded send
92 // attempt; the ledger also rejects a transition racing the final send.
93 if o.SecurityEvents == nil || o.Instance == 0 {
94 return nil
95 }
96 timer := time.NewTimer(time.Millisecond)
97 defer timer.Stop()
98 proof := ports.SecurityOutputProof{Proof: ports.OutputProtection{Generation: state.Generation, Instance: o.Instance, Kind: kind}}
99 for {
100 if o.observeSecurity() || o.securityState != state {
101 return nil
102 }
103 select {
104 case o.SecurityEvents <- proof:
105 return nil
106 case <-ctx.Done():
107 return ctx.Err()
108 case <-timer.C:
109 timer.Reset(time.Millisecond)
110 }
111 }
112}
113
114// Protected frames never enter client-fence KMS paths or capture pipelines.
115// Locker content is composed only after black preparation for this epoch.
116// They never wake core: the pendingFrame has no animating flag, so core falls
117// back to its frame timer while a lock is up. This is intentional.
118func (o *Output) submitProtectedFrame(ctx context.Context, r ports.Renderer, scene ports.Scene, surfaces map[ports.WindowID]ports.SurfaceContent, seen map[ports.WindowID]uint64) error {
119 if !o.securityPrepared || !o.sceneCurrent(scene) {
120 return errSecurityScene
121 }
122 scene.CaptureScene, scene.Capture = nil, nil
123 r.UseTarget(o.back)
124 done, err := r.Render(scene, surfaces)
125 if err != nil {
126 if done != nil {
127 done.Close()
128 }
129 return renderError{err}
130 }
131 // A transition while rendering must not put the obsolete scene on KMS.
132 if !o.sceneCurrent(scene) || !o.protected || !o.securityPrepared {
133 o.holdRead(done)
134 if done != nil {
135 done.Close()
136 }
137 return errSecurityScene
138 }
139 f := pendingFrame{security: scene.Security, frame: true, composed: true, shows: o.shownBy(scene, seen)}
140 err = o.commitWith(o.fbs[o.back], done, false, false, f, overlayWin{})
141 if err != nil {
142 o.holdRead(done)
143 }
144 if done != nil {
145 done.Close()
146 }
147 if err == nil {
148 o.front.forget()
149 o.back = 1 - o.back
150 }
151 return err
152}