#1144143 clevis-initramfs: luks2_decrypt() can never report success (return inside a pipeline subshell)

Package:
clevis
Source:
clevis
Description:
automated encryption framework
Submitter:
Ignacio Martinez
Date:
2026-08-11 15:13:02 UTC
Severity:
normal
#1144143#5
Date:
2026-08-11 15:11:02 UTC
From:
To:
Dear Maintainer,

I ran into this while measuring boot times on a machine with two clevis
pins, and it
took me a while to work out why the console never told me clevis had done
anything.

luks2_decrypt() in /usr/share/initramfs-tools/scripts/local-top/clevis
loops over
LUKS2 tokens using a while-loop on the right-hand side of a pipeline. That
puts the
loop body in a subshell, so the "return 0" on the success path only exits
the
subshell, and control carries on to the trailing "return 1". The function
ends up
reporting failure every time, even when it has already decrypted the
passphrase and
written it to the askpass FIFO.

I checked this against Debian's own package rather than assuming it from
Ubuntu's
build: clevis-initramfs 20-1 from trixie, fetched with "apt-get download"
and
unpacked, has the code below.

Affected code:

    luks2_decrypt() {
        local CRYPTTAB_SOURCE=$1
        local PASSFIFO=$2
        cryptsetup luksDump "$CRYPTTAB_SOURCE" | sed -rn 's|^\s+([0-9]+):
clevis|\1|p' | while read -r id; do
            ...
            echo -n "${decrypted}" >"$PASSFIFO"
            return 0
        done

        return 1
    }

luks1_decrypt() has the same shape via "luksmeta show | while read".

The caller branches on that return value, which leaves the success path
unreachable:

    if luks2_decrypt "${CRYPTTAB_SOURCE}" "${PASSFIFO}"; then
        echo "Unlocked ${CRYPTTAB_SOURCE} with clevis"
    else
        OLD_CRYPTTAB_SOURCE=""
        sleep 5
    fi

Consequences:

 * "Unlocked <device> with clevis" never gets printed, so anyone watching
the console
   has no positive sign that clevis was the thing that worked.
 * Every attempt, successful ones included, clears OLD_CRYPTTAB_SOURCE and
sleeps 5
   seconds before going round again.
 * The retry loop has no way to tell success from failure, since it only
ever sees one
   of the two.

On a healthy boot none of this shows, because local-bottom/clevis kills the
loop as
soon as the volume opens. It surfaces on any host where a pin cannot be
satisfied.

If it helps, here is the shell behaviour on its own. I ran it under /bin/sh
(dash) and
again under bash, with the same result both times:

    shipped() {
        printf '2\n4\n' | while read -r id; do
            return 0
        done
        return 1
    }
    shipped; echo "exit=$?"      # prints exit=1

    fixed() {
        ids=$(printf '2\n4\n')
        for id in $ids; do
            return 0
        done
        return 1
    }
    fixed; echo "exit=$?"        # prints exit=0

Suggested fix — feed the loop from something other than a pipe, so the
function's own
return is reachable:

    luks2_decrypt() {
        local CRYPTTAB_SOURCE=$1
        local PASSFIFO=$2
        local ids
        ids=$(cryptsetup luksDump "$CRYPTTAB_SOURCE" | sed -rn
's|^\s+([0-9]+): clevis|\1|p')
        for id in $ids; do
            ...
            echo -n "${decrypted}" >"$PASSFIFO"
            return 0
        done
        return 1
    }

The same change would apply to luks1_decrypt().

Not applicable upstream, as far as I can tell — initramfs-tools does not
exist in
latchset/clevis, which ships dracut modules with a different
implementation, so this
looks like Debian-family packaging only.

I do not think this is a security issue. When a pin can be satisfied the
volume still
unlocks promptly and correctly; no key material is exposed, no unauthorised
unlock
becomes possible, and nothing cryptographic is weakened. It costs a missing
log line
and some needless retrying.

I have also reported it against Ubuntu, whose clevis 20-1ubuntu0.24.04.1
derives from
this package and carries the identical code, in case the two are useful to
read
together:
https://bugs.launchpad.net/ubuntu/+source/clevis/+bug/2163190

Thanks for your assistance with this!