fix(remote): validate attachment effects defensively
- commit
- 417c304b8e472375752a478c5bd628aad17bdb16
- author
- brice · · 2026-08-08 17:59 UTC
- parent
- 87c2338
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +7 -7
M internal/usecase/daemon/client_frame_routing.go +4 -0
| @@ -278,6 +278,10 @@ func (d *Daemon) ackOutput(token attachmentConnectionToken, epoch, state uint64) | |||
| 278 | 278 | return false | |
| 279 | 279 | } | |
| 280 | 280 | ac.sendMu.Lock() | |
| 281 | + | if ac.output == nil { | |
| 282 | + | ac.sendMu.Unlock() | |
| 283 | + | return false | |
| 284 | + | } | |
| 281 | 285 | acknowledged := ac.output.ack(epoch, state) | |
| 282 | 286 | ac.sendMu.Unlock() | |
| 283 | 287 | if acknowledged { | |
| @@ -278,6 +278,10 @@ func (d *Daemon) ackOutput(token attachmentConnectionToken, epoch, state uint64) | |||||
| 278 | return false | 278 | return false | ||
| 279 | } | 279 | } | ||
| 280 | ac.sendMu.Lock() | 280 | ac.sendMu.Lock() | ||
| 281 | + | if ac.output == nil { | |||
| 282 | + | ac.sendMu.Unlock() | |||
| 283 | + | return false | |||
| 284 | + | } | |||
| 281 | acknowledged := ac.output.ack(epoch, state) | 285 | acknowledged := ac.output.ack(epoch, state) | ||
| 282 | ac.sendMu.Unlock() | 286 | ac.sendMu.Unlock() | ||
| 283 | if acknowledged { | 287 | if acknowledged { | ||
M internal/usecase/daemon/daemon.go +0 -6
| @@ -1154,14 +1154,8 @@ func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut < | |||
| 1154 | 1154 | failAttachment() | |
| 1155 | 1155 | return | |
| 1156 | 1156 | } | |
| 1157 | - | rc := sess.renderCoordinator() | |
| 1158 | - | lease := (*attachmentLease)(nil) | |
| 1159 | - | if rc != nil { | |
| 1160 | - | lease = rc.attachmentLease(ac) | |
| 1161 | - | } | |
| 1162 | 1157 | expected := ac.transportSnapshot() | |
| 1163 | 1158 | welcomeToken := sess.attachmentToken(ac, tr) | |
| 1164 | - | welcomeToken.lease = lease | |
| 1165 | 1159 | welcomeTicket, admitted := ac.beginAttachmentEffect(welcomeToken) | |
| 1166 | 1160 | if expected.transport != tr || !admitted || welcomeToken.ac == nil { | |
| 1167 | 1161 | if admitted { | |
| @@ -1154,14 +1154,8 @@ func (d *Daemon) handleHelloWithContext(handshakeCtx context.Context, timedOut < | |||||
| 1154 | failAttachment() | 1154 | failAttachment() | ||
| 1155 | return | 1155 | return | ||
| 1156 | } | 1156 | } | ||
| 1157 | - | rc := sess.renderCoordinator() | |||
| 1158 | - | lease := (*attachmentLease)(nil) | |||
| 1159 | - | if rc != nil { | |||
| 1160 | - | lease = rc.attachmentLease(ac) | |||
| 1161 | - | } | |||
| 1162 | expected := ac.transportSnapshot() | 1157 | expected := ac.transportSnapshot() | ||
| 1163 | welcomeToken := sess.attachmentToken(ac, tr) | 1158 | welcomeToken := sess.attachmentToken(ac, tr) | ||
| 1164 | - | welcomeToken.lease = lease | |||
| 1165 | welcomeTicket, admitted := ac.beginAttachmentEffect(welcomeToken) | 1159 | welcomeTicket, admitted := ac.beginAttachmentEffect(welcomeToken) | ||
| 1166 | if expected.transport != tr || !admitted || welcomeToken.ac == nil { | 1160 | if expected.transport != tr || !admitted || welcomeToken.ac == nil { | ||
| 1167 | if admitted { | 1161 | if admitted { | ||
M internal/usecase/daemon/picker.go +2 -0
| @@ -251,6 +251,8 @@ func remotePickerReasonText(reason string) string { | |||
| 251 | 251 | return "session stopped" | |
| 252 | 252 | case "session_broken": | |
| 253 | 253 | return "session broken" | |
| 254 | + | case "malformed": | |
| 255 | + | return "catalog malformed" | |
| 254 | 256 | case "refreshing": | |
| 255 | 257 | return "refreshing" | |
| 256 | 258 | case "identity_changed": | |
| @@ -251,6 +251,8 @@ func remotePickerReasonText(reason string) string { | |||||
| 251 | return "session stopped" | 251 | return "session stopped" | ||
| 252 | case "session_broken": | 252 | case "session_broken": | ||
| 253 | return "session broken" | 253 | return "session broken" | ||
| 254 | + | case "malformed": | |||
| 255 | + | return "catalog malformed" | |||
| 254 | case "refreshing": | 256 | case "refreshing": | ||
| 255 | return "refreshing" | 257 | return "refreshing" | ||
| 256 | case "identity_changed": | 258 | case "identity_changed": | ||
M internal/usecase/daemon/remote_picker.go +1 -1
| @@ -130,7 +130,7 @@ func remoteSessionOrigin(host string) string { | |||
| 130 | 130 | } | |
| 131 | 131 | ||
| 132 | 132 | func remoteSessionStateStopped(state string) bool { | |
| 133 | - | return state == "down" || state == "stopped" | |
| 133 | + | return state == "down" | |
| 134 | 134 | } | |
| 135 | 135 | ||
| 136 | 136 | func remotePickerView(key domain.RemoteSessionKey, session ports.RemoteCatalogSession, status remoteHostStatus, fetchedAt time.Time) picker.SessionView { | |
| @@ -130,7 +130,7 @@ func remoteSessionOrigin(host string) string { | |||||
| 130 | } | 130 | } | ||
| 131 | 131 | ||||
| 132 | func remoteSessionStateStopped(state string) bool { | 132 | func remoteSessionStateStopped(state string) bool { | ||
| 133 | - | return state == "down" || state == "stopped" | 133 | + | return state == "down" |
| 134 | } | 134 | } | ||
| 135 | 135 | ||||
| 136 | func remotePickerView(key domain.RemoteSessionKey, session ports.RemoteCatalogSession, status remoteHostStatus, fetchedAt time.Time) picker.SessionView { | 136 | func remotePickerView(key domain.RemoteSessionKey, session ports.RemoteCatalogSession, status remoteHostStatus, fetchedAt time.Time) picker.SessionView { | ||