Deprecations¶
Scratchsmith keeps its contract in COMPATIBILITY.md. Only a major version can change it, and nothing on it disappears without notice. This page lists everything on the way out, and what you write instead.
This page has one list for each kind of change. Two kinds change only in a major version: deprecated input shapes and tolerances. Refused input is a third kind, because a minor release can refuse input that is the attack itself. The last list, Removed in 2.0.0, shows what version 2.0.0 changed after its deprecation cycle ended. Most inputs there now fail, and one gets a new default. It also shows five changes in 2.0.0 with no warning first:
- an exit code
- an output directory that cannot be listed
- an
--includefile that is not a regular file - a program or a library at a package record path
- a
DT_NEEDEDlibrary in a directory whose name holds:
A deprecated input shape still works. Scratchsmith accepts it, prints one warning line to standard error, and exits with the same code as before. The next major version refuses it.
A tolerance is different. Scratchsmith accepts input today that it cannot use in full, and the result can be an image with a part absent. The next major version reports it as an error. Each tolerance prints one warning line to standard error, and the exit code stays the same.
Deprecated input shapes¶
No input shape is deprecated now. Version 2.0.0 removed the 1.x deprecations, and the list Removed in 2.0.0 names each one.
Refused without a deprecation cycle¶
Some input was the attack itself. COMPATIBILITY.md allows a minor or patch release to refuse such input with no deprecation cycle, and this section lists each case.
A library that is not a regular file¶
A DT_NEEDED name can resolve to a FIFO, a device, or a socket. Through 1.7.2, the read of a
FIFO waited forever for a writer, or read a writer's stream with no end. Now pack and graph
refuse such a library before they open it, and exit 1. No library is a FIFO, so no working
image depends on one.
A file that is not an ELF, named by the binary or a library¶
A DT_NEEDED entry in the binary or in a library names a file. The binary's program
interpreter (PT_INTERP) names a file too. Through 1.7.2, scratchsmith staged that file, and
it did not check that the file was an ELF. The name can point at any file on the host, such as
~/.docker/config.json, and that file shipped in the image.
Now pack and graph refuse such a file and exit 1. The loader cannot start a binary that
needs it, so no working image depends on it. A data file that you name with --include still
stages. A library deeper in the tree that names the same file then resolves to that staged copy.
An ELF that the loader cannot load, named by the binary or a library¶
The same names can point at a host file that is an ELF but not a library, such as a core dump. Through 1.7.2, scratchsmith staged that file. A core dump holds the memory of a crashed process, which can include secrets.
Now pack and graph refuse such a file and exit 1. A library must be a shared object
(ET_DYN). glibc refuses any other type with "cannot dynamically load executable", so no
working image depends on one. A program interpreter must be ET_DYN or ET_EXEC, because the
kernel loads both.
This check reads the ELF type only. A PIE executable is ET_DYN too, so a name that points at
one still stages it. A core dump, an object file (ET_REL), and an executable built without
PIE do not. A file that you name with --include stages as before. If the binary itself also
names that file, the refusal comes first.
An archive entry or a piped archive with no size limit¶
unpack holds every blob of an image archive in memory. Through 1.7.2, a large or sparse
archive file let one entry fill memory, and an archive read from a pipe had no total limit.
Now unpack refuses an entry larger than 2 GiB plus 2 MiB, and a pipe that yields more than
16 GiB in all, and exits 1. A gzip layer that decompresses past 2 GiB was already refused, so
an uncompressed layer over 2 GiB is now refused too. An archive file on disk keeps its own size
as the total limit.
There is no way around this limit. unpack cannot read a layer over 2 GiB of either type, and
gzipping the layer does not help, because a gzip layer stops at the same limit.
Tolerances ending in 2.0¶
No tolerance is left now. Version 2.0.0 ended the 1.x tolerances, and the list Removed in 2.0.0 names each one.
Removed in 2.0.0¶
| Input | Warned since | Now | Write this instead |
|---|---|---|---|
A nested [profile.a.profile.b] table |
1.5.0 | An error. pack exits 1 |
A top-level [profile.b] table, with the keys it relied on repeated |
--label NAME with no = |
1.5.0 | A usage error, exit code 2 | --label NAME=value, or --label NAME= for an empty value |
--env NAME with no = |
1.5.0 | A usage error, exit code 2 | --env NAME=value, or --env NAME= for an empty value |
A label or env entry with no = in scratchsmith.toml |
1.5.0 | A usage error, exit code 2 | "NAME=value", or "NAME=" for an empty value |
| A library that starts like an ELF and cannot be parsed | 1.6.0 | An error. pack and graph exit 1 and name the file. 1.x did not warn for a file that is not a regular file, such as a FIFO named with --include. graph now fails on it with no earlier warning. pack already refused it in 1.x |
Replace the damaged file with an intact copy |
A library found only through an inherited RPATH, by an object that has RUNPATH |
1.6.0 | Reported as missing, and pack and graph fail |
Give that object the directory in its own RUNPATH |
A command line refused after the parse, such as --watch --format json |
Not warned | A usage error, exit code 2. 1.x exited 1 | Fix the command line. A script that tests for exactly 1 must also accept 2 |
--packages (the packages key) with no value |
1.9.0 | The default is report,sbom, so --sbom and --scan see the packages and a --scan-fail-on gate can find more |
--packages report (or packages = ["report"]) for the 1.x result |
pack --no-build --output DIR into a DIR that is not empty |
1.6.0 | An error before any file is staged. pack exits 1 |
A new or empty directory, or rm -rf DIR first |
pack --no-build --output DIR into a DIR that cannot be listed |
Not warned | An error. pack exits 1 |
A directory that you can read, or a new one |
$PLATFORM in a search path, on an architecture with no value for it |
1.6.0 | An error. pack and graph exit 1 and name the file that holds the search path |
Write the directory name in the path |
A program interpreter (PT_INTERP) that starts like an ELF and cannot be parsed |
1.8.0 | An error. pack and graph exit 1 and name the file |
Replace the damaged loader with an intact copy |
The program or an --include library at a package record path on the host. This applies to an explicit image in --packages, or an explicit sbom with --sbom or --scan |
Not warned | An error. pack exits 1 and names the path. 1.x exited 0, and the record replaced the file |
Remove sbom and image from --packages, or move the file on the host |
A DT_NEEDED library only in a directory whose name holds :, which ldconfig does not cache |
Not warned | Missing. pack and graph exit 1, and a note: line names the directory. 1.x exited 0, and the image exited 127 at start. An --include file there still stages |
Move the library to a directory with no : in its name |
A library found only through an inherited RPATH¶
An RPATH is inherited: a library that the program loads also searches the program's RPATH.
A library that has its own RUNPATH does not search any inherited RPATH for the libraries
that it needs. Scratchsmith now applies that rule with no fallback, so the library is missing,
as it is for the loader. A note: line on stderr names the directory and the library that
needs the file. To fix it, link the library that needs the file with a RUNPATH that
holds the directory:
A command line refused after the parse exits 2¶
Scratchsmith refuses some command lines only after the parse, because the refusal needs a
second flag or the config. Examples are --watch --format json, pack with no binary, and one
--add-file image path given twice. In 1.x these refusals exited 1, the code for a failed
pack, and printed no warning about a change. In 2.0.0 they exit 2, the same code as an
argument-parse error. This change had no deprecation cycle, because the 1.x contract promised
only a non-zero code for these refusals. A script that tests for exactly 1 must also accept
2.
An output directory that cannot be listed¶
pack --no-build --output DIR checks that DIR is empty. In 1.x the check skipped a DIR
that it had no permission to list, so it printed no warning and packed into it. Version 2.0.0 refuses
that DIR, because it cannot prove that the directory is empty. This change had no warning
before 2.0. Give the pack a directory that you can read, or a new one.
$PLATFORM with no value¶
A library search path is an RPATH or a RUNPATH entry in the binary, or in a library that the
binary needs. It can contain the token $PLATFORM, and the loader replaces that token with the
name of the processor type. Scratchsmith knows that name for x86_64 and aarch64 only.
On any other architecture, the token has no value. Through 1.9, scratchsmith removed the token
and searched what was left, so /opt/app/lib/$PLATFORM became the parent directory
/opt/app/lib/. A library with the same name in that directory then shipped in place of the
correct one. Now pack and graph stop with exit 1 where 1.x warned, and the error names the
file that holds the search path. A search path that the loader never reads still passes, for
example an RPATH in a file that also has RUNPATH. When you link that file, write the
directory name in the search path and do not use the token: