Skip to content

Terraform crashes with nil pointer dereference during backend migration when permission errors occur #38027

@Mark-C-Hall

Description

@Mark-C-Hall

Terraform Version

Latest from main branch (as of 2025-12-23)

Terraform Configuration Files

terraform {
  backend "gcs" {
    bucket = "my-terraform-state-bucket"
    prefix = "live/global/backend"
  }
}

Expected Behavior

When migrating from local backend to GCS backend, if the user lacks sufficient permissions (e.g., storage.objects.get), Terraform should display a clear error message indicating the permission issue.

Actual Behavior

Terraform crashes with a nil pointer dereference panic.

Steps to Reproduce

  1. Create a GCS bucket for Terraform state
  2. Ensure your user has permission to create the bucket but NOT storage.objects.get permission (e.g., only has storage.buckets.create)
  3. Configure a GCS backend in your Terraform configuration
  4. Run terraform init to migrate from local state to GCS backend

Crash Output

❯ tf init -migrate-state
Initializing the backend...

!!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!

Terraform crashed! This is always indicative of a bug within Terraform.
Please report the crash with Terraform[1] so that we can fix this.

When reporting bugs, please include your terraform version, the stack trace
shown below, and any additional information which may help replicate the issue.

[1]: https://github.com/hashicorp/terraform/issues

!!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!

panic: runtime error: invalid memory address or nil pointer dereference
goroutine 1 [running]:
runtime/debug.Stack()
	runtime/debug/stack.go:26 +0x64
github.com/hashicorp/terraform/internal/logging.PanicHandler()
	github.com/hashicorp/terraform/internal/logging/panic.go:84 +0x16c
panic({0x105b0bbc0?, 0x107f97bd0?})
	runtime/panic.go:783 +0x120
github.com/hashicorp/terraform/internal/command.(*Meta).backendMigrateState_s_s(0x140006fe000, 0x1400041cd90)
	github.com/hashicorp/terraform/internal/command/meta_backend_migrate.go:321 +0x768
github.com/hashicorp/terraform/internal/command.(*Meta).backendMigrateState(0x140006fe000, 0x1400041cd90)
	github.com/hashicorp/terraform/internal/command/meta_backend_migrate.go:138 +0x414
github.com/hashicorp/terraform/internal/command.(*Meta).backend_C_r_s(0x140006fe000, 0x140004fc500, 0x7828c893, 0x140007482a0, 0x1400067f418)
	github.com/hashicorp/terraform/internal/command/meta_backend.go:1215 +0xfe4
github.com/hashicorp/terraform/internal/command.(*Meta).backendFromConfig(0x140006fe000, 0x1400049f418)
	github.com/hashicorp/terraform/internal/command/meta_backend.go:780 +0xdd0
github.com/hashicorp/terraform/internal/command.(*Meta).Backend(0x140006fe000, 0x0?)
	github.com/hashicorp/terraform/internal/command/meta_backend.go:119 +0x60
github.com/hashicorp/terraform/internal/command.(*InitCommand).initBackend(0x140006fe000, {0x1061f4b50?, 0x140000fedc0?}, 0x1400070f9e0, {{0x1050446bd?, 0x1028b3fbc?}, 0x1400000f428?}, 0x48, {0x1061fb760, 0x1400011c110})
	github.com/hashicorp/terraform/internal/command/init.go:360 +0xd80
github.com/hashicorp/terraform/internal/command.(*InitCommand).run(0x140006fe000, 0x140006380d0, {0x1061fb760, 0x1400011c110})
	github.com/hashicorp/terraform/internal/command/init_run.go:185 +0xa90
github.com/hashicorp/terraform/internal/command.(*InitCommand).Run(0x140006fe000, {0x1400011a200?, 0x105be05a0?, 0x0?})
	github.com/hashicorp/terraform/internal/command/init.go:74 +0x120
github.com/hashicorp/cli.(*CLI).Run(0x140003ea280)
	github.com/hashicorp/cli@v1.1.7/cli.go:265 +0x420
main.realMain()
	github.com/hashicorp/terraform/main.go:339 +0x1784
main.main()
	github.com/hashicorp/terraform/main.go:64 +0x1c

Root Cause

In internal/command/meta_backend_migrate.go around line 284-323, the code calls opts.Destination.StateMgr() which can return errors in the diagnostics. However, the code only handles the specific case of backend.ErrDefaultWorkspaceNotSupported:

destinationState, sDiags := opts.Destination.StateMgr(opts.destinationWorkspace)
if sDiags.HasErrors() && sDiags.Err().Error() == backend.ErrDefaultWorkspaceNotSupported.Error() {
    // Special handling for workspace not supported...
}
// No handling for OTHER errors - destinationState is nil!
if err := destinationState.RefreshState(); err != nil {  // <- CRASH: nil pointer dereference

When any error OTHER than ErrDefaultWorkspaceNotSupported occurs (such as permission errors, network errors, etc.), the code skips the if block, leaving destinationState as nil, then immediately tries to call destinationState.RefreshState(), causing the panic.

Additional Context

This is similar to #24100 which reported the same crash for the OSS backend. The root cause is identical - the code doesn't properly handle all error cases from StateMgr().

The bug affects any backend migration scenario where StateMgr() returns an error that isn't ErrDefaultWorkspaceNotSupported, including:

Fix Available

I have a fix ready that properly handles all errors from StateMgr() by adding an else clause to return the error immediately instead of allowing the code to continue with a nil destinationState. Will submit a PR shortly.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions