Skip to content

Commit 51408d8

Browse files
committed
Update a cancelableReader importing gpg keys
Signed-off-by: Qi Wang <[email protected]>
1 parent af8a892 commit 51408d8

File tree

7 files changed

+59
-13
lines changed

7 files changed

+59
-13
lines changed

image/copy/single.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"slices"
1313
"strings"
1414
"sync"
15+
"time"
1516

1617
digest "github.com/opencontainers/go-digest"
1718
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
@@ -30,6 +31,8 @@ import (
3031
chunkedToc "go.podman.io/storage/pkg/chunked/toc"
3132
)
3233

34+
const policyEvaluationTimeout = 60 * time.Second
35+
3336
// imageCopier tracks state specific to a single image (possibly an item of a manifest list)
3437
type imageCopier struct {
3538
c *copier
@@ -75,7 +78,11 @@ func (c *copier) copySingleImage(ctx context.Context, unparsedImage *image.Unpar
7578
// Please keep this policy check BEFORE reading any other information about the image.
7679
// (The multiImage check above only matches the MIME type, which we have received anyway.
7780
// Actual parsing of anything should be deferred.)
78-
if allowed, err := c.policyContext.IsRunningImageAllowed(ctx, unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
81+
// Apply a timeout to avoid hanging indefinitely on GPG key import operations. see issues.redhat.com/browse/OCPBUGS-57893
82+
// The timeout context will be used by the cancelable reader to potentially abort GPGME operations.
83+
policyCtx, cancel := context.WithTimeout(ctx, policyEvaluationTimeout)
84+
defer cancel()
85+
if allowed, err := c.policyContext.IsRunningImageAllowed(policyCtx, unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
7986
return copySingleImageResult{}, fmt.Errorf("Source image rejected: %w", err)
8087
}
8188
src, err := image.FromUnparsedImage(ctx, c.options.SourceCtx, unparsedImage)

image/signature/mechanism.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package signature
44

55
import (
66
"bytes"
7+
"context"
78
"errors"
89
"fmt"
910
"io"
@@ -78,7 +79,7 @@ func NewGPGSigningMechanism() (SigningMechanism, error) {
7879
// of these keys.
7980
// The caller must call .Close() on the returned SigningMechanism.
8081
func NewEphemeralGPGSigningMechanism(blob []byte) (SigningMechanism, []string, error) {
81-
return newEphemeralGPGSigningMechanism([][]byte{blob})
82+
return newEphemeralGPGSigningMechanism(context.Background(), [][]byte{blob})
8283
}
8384

8485
// gpgUntrustedSignatureContents returns UNTRUSTED contents of the signature WITHOUT ANY VERIFICATION,

image/signature/mechanism_gpgme_only.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
package signature
44

55
import (
6+
"bytes"
7+
"context"
8+
"io"
69
"os"
710

811
"github.com/proglottis/gpgme"
@@ -12,7 +15,7 @@ import (
1215
// recognizes _only_ public keys from the supplied blobs, and returns the identities
1316
// of these keys.
1417
// The caller must call .Close() on the returned SigningMechanism.
15-
func newEphemeralGPGSigningMechanism(blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
18+
func newEphemeralGPGSigningMechanism(ctx context.Context, blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
1619
dir, err := os.MkdirTemp("", "containers-ephemeral-gpg-")
1720
if err != nil {
1821
return nil, nil, err
@@ -23,34 +26,61 @@ func newEphemeralGPGSigningMechanism(blobs [][]byte) (signingMechanismWithPassph
2326
os.RemoveAll(dir)
2427
}
2528
}()
26-
ctx, err := newGPGMEContext(dir)
29+
gpgmeCtx, err := newGPGMEContext(dir)
2730
if err != nil {
2831
return nil, nil, err
2932
}
3033
keyIdentities := []string{}
3134
for _, blob := range blobs {
32-
ki, err := importKeysFromBytes(ctx, blob)
35+
ki, err := importKeysFromBytes(ctx, gpgmeCtx, blob)
3336
if err != nil {
3437
return nil, nil, err
3538
}
3639
keyIdentities = append(keyIdentities, ki...)
3740
}
3841

39-
mech := newGPGMESigningMechanism(ctx, dir)
42+
mech := newGPGMESigningMechanism(gpgmeCtx, dir)
4043
removeDir = false
4144
return mech, keyIdentities, nil
4245
}
4346

47+
// cancelableReader wraps an io.Reader and checks context cancellation on each Read call.
48+
type cancelableReader struct {
49+
ctx context.Context
50+
reader io.Reader
51+
}
52+
53+
func (r *cancelableReader) Read(p []byte) (int, error) {
54+
// Check if context is cancelled before each read
55+
if err := r.ctx.Err(); err != nil {
56+
return 0, err
57+
}
58+
n, err := r.reader.Read(p)
59+
// Check again after read in case cancellation happened during the read
60+
if err == nil && r.ctx.Err() != nil {
61+
return n, r.ctx.Err()
62+
}
63+
return n, err
64+
}
65+
4466
// importKeysFromBytes imports public keys from the supplied blob and returns their identities.
4567
// The blob is assumed to have an appropriate format (the caller is expected to know which one).
4668
// NOTE: This may modify long-term state (e.g. key storage in a directory underlying the mechanism);
4769
// but we do not make this public, it can only be used through newEphemeralGPGSigningMechanism.
48-
func importKeysFromBytes(ctx *gpgme.Context, blob []byte) ([]string, error) {
49-
inputData, err := gpgme.NewDataBytes(blob)
70+
// The context can be used to cancel the operation; if cancelled, the reader will return an error
71+
// which may allow the GPGME operation to abort (though there's no guarantee the C library will
72+
// respect this immediately).
73+
func importKeysFromBytes(ctx context.Context, gpgmeCtx *gpgme.Context, blob []byte) ([]string, error) {
74+
// Create a cancelable reader that checks context on each Read call
75+
reader := &cancelableReader{
76+
ctx: ctx,
77+
reader: bytes.NewReader(blob),
78+
}
79+
inputData, err := gpgme.NewDataReader(reader)
5080
if err != nil {
5181
return nil, err
5282
}
53-
res, err := ctx.Import(inputData)
83+
res, err := gpgmeCtx.Import(inputData)
5484
if err != nil {
5585
return nil, err
5686
}

image/signature/mechanism_openpgp.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package signature
44

55
import (
66
"bytes"
7+
"context"
78
"errors"
89
"fmt"
910
"io"
@@ -63,7 +64,9 @@ func newGPGSigningMechanismInDirectory(optionalDir string) (signingMechanismWith
6364
// recognizes _only_ public keys from the supplied blob, and returns the identities
6465
// of these keys.
6566
// The caller must call .Close() on the returned SigningMechanism.
66-
func newEphemeralGPGSigningMechanism(blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
67+
// The context parameter is accepted for API consistency but is not used by this implementation.
68+
func newEphemeralGPGSigningMechanism(ctx context.Context, blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
69+
_ = ctx // Context not used in this implementation
6770
m := &openpgpSigningMechanism{
6871
keyring: openpgp.EntityList{},
6972
}

image/signature/mechanism_sequoia.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
package signature
44

55
import (
6+
"context"
7+
68
"go.podman.io/image/v5/signature/internal/sequoia"
79
)
810

@@ -18,7 +20,9 @@ type sequoiaEphemeralSigningMechanism struct {
1820
// recognizes _only_ public keys from the supplied blobs, and returns the identities
1921
// of these keys.
2022
// The caller must call .Close() on the returned SigningMechanism.
21-
func newEphemeralGPGSigningMechanism(blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
23+
// The context parameter is accepted for API consistency but is not used by this implementation.
24+
func newEphemeralGPGSigningMechanism(ctx context.Context, blobs [][]byte) (signingMechanismWithPassphrase, []string, error) {
25+
_ = ctx // Context not used in this implementation
2226
if err := sequoia.Init(); err != nil {
2327
return nil, nil, err // Coverage: This is impractical to test in-process, with the static go_sequoia_dlhandle.
2428
}

image/signature/mechanism_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package signature
44

55
import (
66
"bytes"
7+
"context"
78
"os"
89
"path/filepath"
910
"slices"
@@ -175,7 +176,7 @@ func TestNewEphemeralGPGSigningMechanism(t *testing.T) {
175176
require.NoError(t, err)
176177
keyBlob2, err := os.ReadFile("./fixtures/public-key-2.gpg")
177178
require.NoError(t, err)
178-
mech, keyIdentities, err = newEphemeralGPGSigningMechanism([][]byte{keyBlob1, keyBlob2})
179+
mech, keyIdentities, err = newEphemeralGPGSigningMechanism(context.Background(), [][]byte{keyBlob1, keyBlob2})
179180
require.NoError(t, err)
180181
defer mech.Close()
181182
assert.Equal(t, []string{TestKeyFingerprint, TestKeyFingerprintWithPassphrase}, keyIdentities)

image/signature/policy_eval_signedby.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (pr *prSignedBy) isSignatureAuthorAccepted(ctx context.Context, image priva
4040
}
4141

4242
// FIXME: move this to per-context initialization
43-
mech, trustedIdentities, err := newEphemeralGPGSigningMechanism(data)
43+
mech, trustedIdentities, err := newEphemeralGPGSigningMechanism(ctx, data)
4444
if err != nil {
4545
return sarRejected, nil, err
4646
}

0 commit comments

Comments
 (0)