anchore / syft

CLI tool and library for generating a Software Bill of Materials from container images and filesystems
Apache License 2.0
6.12k stars 563 forks source link

Syft fails when /tmp is missing, but continues without exit code 0 #3074

Open devfbe opened 2 months ago

devfbe commented 2 months ago

What happened: I ran syft in an environment where no /tmp folder existed (a really small scratch container). In this container I tried to scan a container image tar file.

Command: syft scan container.tar -o cyclonedx-json=sbom.json

Syft logs the error:

[0000]  WARN file could not be unarchived: unable to create tempdir for archive processing: stat /tmp: no such file or directory

but then continues and returns with exit code 0. The generated SBOM does not contain any dependency but the structure is valid.

What you expected to happen: I expect that syft either crashes with exit code != 0 and does not generate a sbom in this case or that it creates the tmp folder when it does not exist.

Steps to reproduce the issue: Run syft in a docker container and scan a container image tar there. Delete the /tmp/ folder before, then you should be able to reproduce the issue.

Anything else we need to know?: We ran this in GitLab and the error message [0000] WARN file could not be unarchived: unable to create tempdir for archive processing: stat /tmp: no such file or directory was not visible in the pipeline, it only appeared when running manually, maybe because a tty was missing?

Environment:

popey commented 2 months ago

Thanks for the issue @devfbe - what a fun one! I was able to reproduce the issue using the steps you provided - rm'ing /tmp.

However, you should have either /tmp or $TMPDIR set, as it's part of the posix spec.

The following directories shall exist on conforming systems and conforming applications shall make use of them only as described. Strictly conforming applications shall not assume the ability to create files in any of these directories, unless specified below.

...

/tmp - A directory made available for applications that need a place to create temporary files. Applications shall be allowed to create files in this directory, but shall not assume that such files are preserved between invocations of the application.

So, perhaps obviously, but for completeness, I think it's probably wise to have a /tmp or at least point $TMPDIR somewhere. I don't think it should be the responsibility of syft to create the /tmp directory, should it not exist. Other applications don't do this either. For example, apt breaks without /tmp or $TMPDIR.

root@f5dbc629b889:~# ls /tmp
ls: cannot access '/tmp': No such file or directory
root@f5dbc629b889:~# unset TMPDIR
root@f5dbc629b889:~# apt update
Hit:1 http://security.ubuntu.com/ubuntu noble-security InRelease
Hit:2 http://archive.ubuntu.com/ubuntu noble InRelease
Hit:3 http://archive.ubuntu.com/ubuntu noble-updates InRelease
Err:1 http://security.ubuntu.com/ubuntu noble-security InRelease
  Couldn't create temporary file /tmp/apt.conf.HL0CTW for passing config to apt-key
Err:2 http://archive.ubuntu.com/ubuntu noble InRelease
  Couldn't create temporary file /tmp/apt.conf.SkCa2d for passing config to apt-key
Err:3 http://archive.ubuntu.com/ubuntu noble-updates InRelease
  Couldn't create temporary file /tmp/apt.conf.w5hJ1N for passing config to apt-key
Hit:4 http://archive.ubuntu.com/ubuntu noble-backports InRelease
Err:4 http://archive.ubuntu.com/ubuntu noble-backports InRelease
  Couldn't create temporary file /tmp/apt.conf.g9n5dB for passing config to apt-key
Reading package lists... Error!
...
E: Sub-process returned an error code
E: Unable to mkstemp /tmp/clearsigned.message.lNweyx - GetTempFile (2: No such file or directory)
E: The package lists or status file could not be parsed or opened.

Within syft, it looks like we use os.MkdirTemp which expects TempDir to return something sensible. On Linux, that means $TMPDIR points somewhere.

The most straightforward workaround thus would be to set TMPDIR before launching syft. In a quick test in a container where I rm'ed the /tmp directory, then pointing TMPDIR somewhere works (in that we don't get the warning anymore). For example, below, I point it at $PWD - wherever syft is running, but it could point anywhere you have write permissions.

$ TMPDIR=$PWD syft scan nginx.tar -o cyclonedx-json=sbom-tmpdir-created-and-env-set-to-pwd.json
$ jq . sbom-tmpdir-created-and-env-set-to-pwd.json | head
{
  "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "serialNumber": "urn:uuid:b988d2e2-6c26-40c2-ac25-e7306b19867a",
  "version": 1,
  "metadata": {
    "timestamp": "2024-07-29T08:55:09Z",
    "tools": {
      "components": [

I agree that we should probably exit non-zero when we cannot even unpack the target being scanned. (despite me thinking this particular trigger is a bit of a weird non-standard, non-compliant edge-case that /tmp doesn't exist, and $TMPDIR points nowhere useful) 😅

The three places we call saveArchiveToTemp don't appear to do the right thing here.

Just my 2p though.

devfbe commented 2 months ago

Really good point @popey, I had a bad feeling when suggesting that syft creates the TMPDIR, now I know why. Of course the environment is broken when no TMPDIR is set or it points to the wrong destination. I'll fix this in my setting by fixing the TMPDIR.

Additionally, I would love to see that syft terminates with exit code 1. I would try to open a merge request for that.

I think that a broken tmp dir write is a good reason to fail immediately because something is really wrong / broken in the environment - instead of returning an err. Would you agree with that? Than I would implement failing "in place" at the affected locations in syft.

devfbe commented 2 months ago

Additionally I was able to reproduce that important WARN logs are missing, when no tty is there. Is this intended?

How to reproduce (containerimage.tar is a test oci tar, syft is 1.9.0):

FROM scratch
COPY syft /
COPY containerimage.tar /
ENTRYPOINT ["/syft"]
podman run -i syft scan /containerimage.tar

Output:

No packages discovered

With -t:

podman run -ti syft scan /containerimage.tar

Output:

 podman run -ti syft scan /containerimage.tar
 ✔ Indexed file system                                                                                                                                                                                    /
 ✔ Cataloged contents                                                                                                                      ccf875aa8af6073319964e9d36823bf7337a7ca8caf5c386c818452a7307c45f
   ├── ✔ Packages                        [0 packages]
   └── ✔ Executables                     [0 executables]
[0000]  WARN file could not be unarchived: unable to create tempdir for archive processing: stat /tmp: no such file or directory
[0001]  WARN unable to determine GOPATH or user home dir: %!w(string=exec: "getent": executable file not found in $PATH)
[0001]  WARN unable to determine GOPATH or user home dir: %!w(string=exec: "getent": executable file not found in $PATH)
No packages discovered

As you can see, important messages are not there. I would expect that logging always works, when /dev/stderr and /dev/stdout are present, unrelated to the existance of a pseudo tty. I'll file another bug for that to keep this issue "clean" and focussed on the exit 1 when /tmp is missing or $TMPDIR is broken.