Skip to content

Fix resource leaks in provisioners and commands - #38585

Merged
SarahFrench merged 3 commits into
hashicorp:mainfrom
SebTardif:fix/resource-leaks
May 14, 2026
Merged

SarahFrench merged 3 commits into
hashicorp:mainfrom
SebTardif:fix/resource-leaks

Conversation

@SebTardif

Copy link
Copy Markdown

Closes #38575

Fix four resource leaks where file handles, HTTP response bodies, or pipe
file descriptors were not properly closed on all code paths.

internal/command/workspace_new.go -- The file opened via os.Open for
reading existing state (when using terraform workspace new -state=FILE)
was never closed. Added defer f.Close().

internal/builtin/provisioners/file/resource_provisioner.go -- The
*os.File handle from ioutil.TempFile in getSrc() was never closed.
The caller re-opens the file by path, so the original handle must be closed
to avoid leaking one file descriptor per file-provisioner invocation.
Added explicit file.Close() before both return paths.

internal/command/login.go -- The defer resp.Body.Close() was placed
after ioutil.ReadAll(resp.Body), meaning the body leaked if ReadAll
returned an error (early return at line 269). Moved the defer to
immediately after the resp != nil check, matching idiomatic Go patterns.

internal/builtin/provisioners/local-exec/resource_provisioner.go --
os.Pipe() creates two file descriptors. The write end (pw) was
closed at line 187, but the read end (pr) was never closed, leaking
one file descriptor per local-exec invocation. Added pr.Close()
after the copy goroutine completes.

Target Release

1.16.x

Rollback Plan

  • If a change needs to be reverted, we will roll out an update to the code within 7 days.

Changes to Security Controls

No changes to security controls.

AI Disclosure

This fix was developed with AI assistance (Grok by xAI) in a human-in-the-loop workflow. The author identified the bug, directed the investigation, reviewed all code changes, and verified correctness. AI was used to assist with code analysis and drafting the fix.

CHANGELOG entry

  • This change is not user-facing.

Sebastien Tardif added 2 commits May 7, 2026 22:06
Close file handles, HTTP response bodies, and pipe ends that were
previously leaked on certain code paths:

- workspace_new.go: Close state file opened via os.Open
- file/resource_provisioner.go: Close temp file handle before returning
  the file path to caller
- login.go: Move defer resp.Body.Close() before ioutil.ReadAll so the
  body is closed even when ReadAll fails
- local-exec/resource_provisioner.go: Close the read end of os.Pipe
  after the copy goroutine completes

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

@SarahFrench SarahFrench left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @SebTardif thanks for your contribution. I've confirmed that the changes you have made either have test coverage or aren't easily able to be covered by tests.

Before I approve could you please remove the change file from your PR. As your GitHub issue noted, this in an internal code issue and doesn't have user-facing impact.

Comment on lines +197 to +198
pr.Close()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed this has test coverage, e.g. in TestResourceProvider_Apply

Comment on lines 140 to 146
if _, err = file.WriteString(content.AsString()); err != nil {
file.Close()
return "", true, err
}

file.Close()
return file.Name(), true, nil

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These don't have test coverage but that's ok for this PR; testing the file provisioner end to end would require a mock SSH server which we should not implement in this PR.

Comment thread internal/command/login.go
Comment on lines +264 to -272
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.logMOTDError(err)
c.outputDefaultTFCLoginSuccess()
return 0
}

defer resp.Body.Close()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimal change, and has test coverage in TestLogin

Comment on lines 148 to +149
}
defer f.Close()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has test coverage in TestWorkspace_createWithState

Internal code fix with no user-facing impact.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@SarahFrench SarahFrench added the no-changelog-needed Add this to your PR if the change does not require a changelog entry label May 14, 2026
@SarahFrench
SarahFrench merged commit 3c94ef6 into hashicorp:main May 14, 2026
8 of 9 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jun 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

no-changelog-needed Add this to your PR if the change does not require a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resource leaks: unclosed file handles in workspace_new, file provisioner, login, and local-exec

2 participants