Skip to content

Commit c6366e5

Browse files
Emyrkmafredri
andauthored
chore: prevent nil derefs in non-critical paths (#11411)
* chore: prevent nil derefs in non-critical paths --------- Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
1 parent 85ff030 commit c6366e5

File tree

1 file changed

+24
-5
lines changed

1 file changed

+24
-5
lines changed

cli/cliui/agent.go

+24-5
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,12 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
200200

201201
switch agent.LifecycleState {
202202
case codersdk.WorkspaceAgentLifecycleReady:
203-
sw.Complete(stage, agent.ReadyAt.Sub(*agent.StartedAt))
203+
sw.Complete(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
204204
case codersdk.WorkspaceAgentLifecycleStartTimeout:
205205
sw.Fail(stage, 0)
206206
sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script timed out and your workspace may be incomplete.")
207207
case codersdk.WorkspaceAgentLifecycleStartError:
208-
sw.Fail(stage, agent.ReadyAt.Sub(*agent.StartedAt))
208+
sw.Fail(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
209209
// Use zero time (omitted) to separate these from the startup logs.
210210
sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script exited with an error and your workspace may be incomplete.")
211211
sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates#startup-script-exited-with-an-error"))
@@ -221,7 +221,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
221221
case agent.LifecycleState.ShuttingDown():
222222
// We no longer know if the startup script failed or not,
223223
// but we need to tell the user something.
224-
sw.Complete(stage, agent.ReadyAt.Sub(*agent.StartedAt))
224+
sw.Complete(stage, safeDuration(sw, agent.ReadyAt, agent.StartedAt))
225225
return errAgentShuttingDown
226226
}
227227
}
@@ -238,13 +238,13 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
238238
sw.Log(time.Now(), codersdk.LogLevelWarn, "Wait for it to reconnect or restart your workspace.")
239239
sw.Log(time.Now(), codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates#agent-connection-issues"))
240240

241-
disconnectedAt := *agent.DisconnectedAt
241+
disconnectedAt := agent.DisconnectedAt
242242
for agent.Status == codersdk.WorkspaceAgentDisconnected {
243243
if agent, err = fetch(); err != nil {
244244
return xerrors.Errorf("fetch: %w", err)
245245
}
246246
}
247-
sw.Complete(stage, agent.LastConnectedAt.Sub(disconnectedAt))
247+
sw.Complete(stage, safeDuration(sw, agent.LastConnectedAt, disconnectedAt))
248248
}
249249
}
250250
}
@@ -257,6 +257,25 @@ func troubleshootingMessage(agent codersdk.WorkspaceAgent, url string) string {
257257
return m
258258
}
259259

260+
// safeDuration returns a-b. If a or b is nil, it returns 0.
261+
// This is because we often dereference a time pointer, which can
262+
// cause a panic. These dereferences are used to calculate durations,
263+
// which are not critical, and therefor should not break things
264+
// when it fails.
265+
// A panic has been observed in a test.
266+
func safeDuration(sw *stageWriter, a, b *time.Time) time.Duration {
267+
if a == nil || b == nil {
268+
if sw != nil {
269+
// Ideally the message includes which fields are <nil>, but you can
270+
// use the surrounding log lines to figure that out. And passing more
271+
// params makes this unwieldy.
272+
sw.Log(time.Now(), codersdk.LogLevelWarn, "Warning: Failed to calculate duration from a time being <nil>.")
273+
}
274+
return 0
275+
}
276+
return a.Sub(*b)
277+
}
278+
260279
type closeFunc func() error
261280

262281
func (c closeFunc) Close() error {

0 commit comments

Comments
 (0)