hashicorp/packer · error
scanner binary is not executable at %s after chmod; check th
Error message
scanner binary is not executable at %s after chmod; check that /tmp is not mounted noexec on the remote host
What it means
This static error is returned when the post-chmod verification (`test -x`) fails on the remote Unix host, meaning the scanner binary at /tmp/packer-sbom-runner is still not executable even though `chmod +x` itself reported success. It is thrown at provisioner/hcp-sbom/provisioner.go:551 in uploadScanner. It exists to give an actionable hint: the usual cause is /tmp mounted with the noexec option, so any binary there cannot be executed regardless of permission bits.
Source
Thrown at provisioner/hcp-sbom/provisioner.go:551
}
// Step 2: upload binary directly to remote.
localFile := bytes.NewReader(binaryData)
log.Printf("[INFO] Uploading Packer binary to %s...", remotePath)
if err := comm.Upload(remotePath, localFile, nil); err != nil {
return "", fmt.Errorf("failed to upload Packer binary: %s", err)
}
// Step 3: make it executable.
chmodCmd := fmt.Sprintf(`chmod +x "%s"`, remotePath)
if err := p.runRemoteCmd(ctx, comm, chmodCmd, "chmod scanner binary"); err != nil {
return "", err
}
// Final verify: confirm binary is executable.
verifyCmd := fmt.Sprintf(`test -x "%s"`, remotePath)
if err := p.runRemoteCmd(ctx, comm, verifyCmd, "verify scanner is executable"); err != nil {
return "", fmt.Errorf("scanner binary is not executable at %s after chmod; "+
"check that /tmp is not mounted noexec on the remote host", remotePath)
}
}
return remotePath, nil
}
func extractBinaryFromZip(zipPath, binaryName string) ([]byte, error) {
zr, err := zip.OpenReader(zipPath)
if err != nil {
return nil, fmt.Errorf("failed to open zip: %w", err)
}
defer func() { _ = zr.Close() }()
for _, f := range zr.File {
if f.Name != binaryName {
continue
}View on GitHub (pinned to eb36e3c3e4)
Solutions
- Check the remote mount flags: `findmnt -no OPTIONS /tmp` — if `noexec` is present, remount writable+exec (`sudo mount -o remount,exec /tmp`) or bake it into the image.
- Change the provisioner's remote install location to an exec-allowed path if /tmp must stay noexec (requires adjusting remotePath in the hcp-sbom provisioner config/code).
- Check for cleanup agents (systemd-tmpfiles-clean, security daemons) removing the binary, and re-run to see if timing is the issue.
- Verify file ownership/permissions on the guest: `ls -l /tmp/packer-sbom-runner` as the SSH user; ensure the user owns it.
- Run with PACKER_LOG=1 to see the raw output of the failing `test -x` command and distinguish permission vs missing-file causes.
Example fix
// before (host /etc/fstab, hardened image) tmpfs /tmp tmpfs rw,nosuid,nodev,noexec 0 0 // after sudo mount -o remount,exec /tmp # or in /etc/fstab: tmpfs /tmp tmpfs rw,nosuid,nodev 0 0
Defensive patterns
Strategy: validation
Validate before calling
// pre-flight inside the provisioner, before uploading:
checkCmd := `findmnt -no OPTIONS /tmp || mount | grep ' on /tmp '`
out, err := runRemoteCmd(ctx, comm, checkCmd)
if err != nil {
return fmt.Errorf("cannot inspect /tmp mount options: %w", err)
}
if strings.Contains(out, "noexec") {
return fmt.Errorf("/tmp is mounted noexec; remount exec or install elsewhere")
}
// and verify write+exec capability early:
probeCmd := `f=$(mktemp); printf '#!/bin/sh\n' > $f; chmod +x $f; $f; rc=$?; rm -f $f; exit $rc` Type guard
func isExecAllowed(mountOptions string) bool {
return !strings.Contains(mountOptions, "noexec")
} Try / catch
err := p.runRemoteCmd(ctx, comm, verifyCmd, "verify scanner is executable")
if err != nil {
// inspect mount options to confirm the noexec hypothesis before remounting
return fmt.Errorf("scanner not executable at %s: %w (check: findmnt -no OPTIONS /tmp)", remotePath, err)
} Prevention
- Check `findmnt -no OPTIONS /tmp` on guest images before adopting them for builds; strip noexec if present.
- Install binaries into an exec-allowed path (e.g. /usr/local/bin) on noexec-hardened images instead of /tmp.
- Disable or tune tmpfiles cleanup agents that purge /tmp during long builds.
- Verify ownership and umask effects: chmod +x immediately before execution, as the same SSH user.
- Bake the exec-friendly mount options into the base image so every build inherits them.
When it happens
Trigger: p.runRemoteCmd(ctx, comm, `test -x "/tmp/packer-sbom-runner"`, ...) returns non-zero after a successful chmod +x — the file exists but lacks the execute bit, or the filesystem enforces noexec so test -x still reflects unexecutable permissions, or the file was removed between chmod and verify.
Common situations: /tmp is mounted noexec on hardened VM images (very common on CIS-baselined hosts and some cloud images); a security agent or systemd-tmpfiles cleanup deletes files from /tmp between steps; a custom umask or ACL strips the x bit right after chmod; the SSH user lacks ownership of the file so test -x fails.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- request GitHub OIDC token: unexpected status %s
- load KMS public key %q: %w
- unable to fetch project If the provided credentials are tie
- could not create plugin folder %q: %w
- could not create final plugin binary file: %w
AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05).
Data as JSON: /api/errors/2a7f28f6eda3d9a3.
Report an issue: GitHub.