{"record":{"id":"2a7f28f6eda3d9a3","repo":"hashicorp/packer","slug":"scanner-binary-is-not-executable-at-s-after-chmod","errorCode":null,"errorMessage":"scanner binary is not executable at %s after chmod; check that /tmp is not mounted noexec on the remote host","messagePattern":"scanner binary is not executable at (.+?) after chmod; check that /tmp is not mounted noexec on the remote host","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"provisioner/hcp-sbom/provisioner.go","lineNumber":551,"sourceCode":"\t\t}\n\n\t\t// Step 2: upload binary directly to remote.\n\t\tlocalFile := bytes.NewReader(binaryData)\n\t\tlog.Printf(\"[INFO] Uploading Packer binary to %s...\", remotePath)\n\t\tif err := comm.Upload(remotePath, localFile, nil); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to upload Packer binary: %s\", err)\n\t\t}\n\n\t\t// Step 3: make it executable.\n\t\tchmodCmd := fmt.Sprintf(`chmod +x \"%s\"`, remotePath)\n\t\tif err := p.runRemoteCmd(ctx, comm, chmodCmd, \"chmod scanner binary\"); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// Final verify: confirm binary is executable.\n\t\tverifyCmd := fmt.Sprintf(`test -x \"%s\"`, remotePath)\n\t\tif err := p.runRemoteCmd(ctx, comm, verifyCmd, \"verify scanner is executable\"); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"scanner binary is not executable at %s after chmod; \"+\n\t\t\t\t\"check that /tmp is not mounted noexec on the remote host\", remotePath)\n\t\t}\n\t}\n\n\treturn remotePath, nil\n}\n\nfunc extractBinaryFromZip(zipPath, binaryName string) ([]byte, error) {\n\tzr, err := zip.OpenReader(zipPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open zip: %w\", err)\n\t}\n\tdefer func() { _ = zr.Close() }()\n\n\tfor _, f := range zr.File {\n\t\tif f.Name != binaryName {\n\t\t\tcontinue\n\t\t}","sourceCodeStart":533,"sourceCodeEnd":569,"githubUrl":"https://github.com/hashicorp/packer/blob/eb36e3c3e48a036f3e8cc94087636ee72e1303c9/provisioner/hcp-sbom/provisioner.go#L533-L569","documentation":"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.","triggerScenarios":"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.","commonSituations":"/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.","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."],"exampleFix":"// before (host /etc/fstab, hardened image)\ntmpfs /tmp tmpfs rw,nosuid,nodev,noexec 0 0\n// after\nsudo mount -o remount,exec /tmp\n# or in /etc/fstab: tmpfs /tmp tmpfs rw,nosuid,nodev 0 0","handlingStrategy":"validation","validationCode":"// pre-flight inside the provisioner, before uploading:\ncheckCmd := `findmnt -no OPTIONS /tmp || mount | grep ' on /tmp '`\nout, err := runRemoteCmd(ctx, comm, checkCmd)\nif err != nil {\n\treturn fmt.Errorf(\"cannot inspect /tmp mount options: %w\", err)\n}\nif strings.Contains(out, \"noexec\") {\n\treturn fmt.Errorf(\"/tmp is mounted noexec; remount exec or install elsewhere\")\n}\n// and verify write+exec capability early:\nprobeCmd := `f=$(mktemp); printf '#!/bin/sh\\n' > $f; chmod +x $f; $f; rc=$?; rm -f $f; exit $rc`","typeGuard":"func isExecAllowed(mountOptions string) bool {\n\treturn !strings.Contains(mountOptions, \"noexec\")\n}","tryCatchPattern":"err := p.runRemoteCmd(ctx, comm, verifyCmd, \"verify scanner is executable\")\nif err != nil {\n\t// inspect mount options to confirm the noexec hypothesis before remounting\n\treturn fmt.Errorf(\"scanner not executable at %s: %w (check: findmnt -no OPTIONS /tmp)\", remotePath, err)\n}","preventionTips":["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."],"tags":["unix","permissions","chmod","noexec","tmp"],"backgroundTag":"permission-denied","analyzedSha":"eb36e3c3e48a036f3e8cc94087636ee72e1303c9","analyzedAt":"2026-09-05T13:20:43.127Z","contentChangedAt":"2026-09-05T13:20:43.127Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}