mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-26 06:16:56 +09:00
Bound the memory used when parsing uploaded packages, so instances with low memory limits survive non-standard packages. 1. Metadata files are read through a size-limited reader, archives themselves stay unbounded so large payloads keep working 2. zstd decoding caps the window at 128 MiB, the zstd CLI default 3. RPM headers go through a first-party reader with rpmlib's size and array count limits that allocates only what the upload contains, until https://github.com/sassoftware/go-rpmutils/pull/44 is released. Signing still verifies the payload digest, and its signature header matches rpmsign's byte for byte 4. Swift and NuGet read the zip entry they size-checked, a duplicate name could stand in for it before 5. Terraform states check `LIMIT_SIZE_TERRAFORM_STATE` before parsing, NuGet symbol PDBs share a 256 MiB budget 6. Oversized container configs are rejected instead of stored without metadata, invalid Vagrant boxes answer 400 instead of 500 Signed-off-by: techknowlogick <git@tklk.dev> Co-authored-by: techknowlogick <git@tklk.dev> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: techknowlogick <techknowlogick@gitea.com>
127 lines
3.9 KiB
Go
127 lines
3.9 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package rpm
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/md5"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"hash"
|
|
"io"
|
|
"maps"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
|
)
|
|
|
|
var payloadDigestHashes = map[uint64]func() hash.Hash{1: md5.New, 2: sha1.New, 8: sha256.New, 9: sha512.New384, 10: sha512.New, 11: sha256.New224} // rpm's PGPHASHALGO ids
|
|
|
|
// SignPackage verifies the payload and replaces all OpenPGP signatures with a header-only and a header+payload one, the latter needed by packages without payload digest
|
|
func SignPackage(r io.ReadSeeker, key *packet.PrivateKey) (io.Reader, error) {
|
|
lead, sig, h, err := readHeaders(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
payloadStart, err := r.Seek(0, io.SeekCurrent)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payloadHash, payloadDigest, err := payloadVerifier(sig, h)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
headerHash, packageHash := crypto.SHA256.New(), crypto.SHA256.New()
|
|
headerHash.Write(h.raw)
|
|
packageHash.Write(h.raw)
|
|
if _, err := io.Copy(io.MultiWriter(packageHash, payloadHash), r); err != nil {
|
|
return nil, err
|
|
}
|
|
if hex.EncodeToString(payloadHash.Sum(nil)) != payloadDigest {
|
|
return nil, ErrInvalidPackage
|
|
}
|
|
if _, err := r.Seek(payloadStart, io.SeekStart); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
headerSignature, err := signOpenPGP(key, headerHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
packageSignature, err := signOpenPGP(key, packageHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, tag := range []uint32{sigTagDSA, sigTagGPG, sigTagReservedSpace} {
|
|
delete(sig.entries, tag)
|
|
}
|
|
sig.entries[sigTagRSA] = headerEntry{typ: typeBin, count: uint32(len(headerSignature)), data: headerSignature}
|
|
sig.entries[sigTagPGP] = headerEntry{typ: typeBin, count: uint32(len(packageSignature)), data: packageSignature}
|
|
|
|
return io.MultiReader(bytes.NewReader(lead), bytes.NewReader(sig.marshalSignature()), bytes.NewReader(h.raw), r), nil
|
|
}
|
|
|
|
func payloadVerifier(sig, h *header) (hash.Hash, string, error) {
|
|
if newHash, ok := payloadDigestHashes[h.getUint(tagPayloadDigestAlgo)]; ok {
|
|
if digest := h.getString(tagPayloadDigest); digest != "" {
|
|
return newHash(), digest, nil
|
|
}
|
|
}
|
|
if md5sum := sig.entries[sigTagMD5]; md5sum.typ == typeBin && len(md5sum.data) > 0 {
|
|
hasher := md5.New()
|
|
hasher.Write(h.raw)
|
|
return hasher, hex.EncodeToString(md5sum.data), nil
|
|
}
|
|
return nil, "", ErrInvalidPackage
|
|
}
|
|
|
|
func signOpenPGP(key *packet.PrivateKey, digest hash.Hash) ([]byte, error) {
|
|
signature := &packet.Signature{
|
|
SigType: packet.SigTypeBinary,
|
|
CreationTime: time.Now(),
|
|
PubKeyAlgo: key.PubKeyAlgo,
|
|
Hash: crypto.SHA256,
|
|
IssuerKeyId: &key.KeyId,
|
|
}
|
|
if err := signature.Sign(digest, key, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
var buf bytes.Buffer
|
|
err := signature.Serialize(&buf)
|
|
return buf.Bytes(), err
|
|
}
|
|
|
|
func (h *header) marshalSignature() []byte {
|
|
tags := slices.DeleteFunc(slices.Sorted(maps.Keys(h.entries)), func(tag uint32) bool { return tag <= maxRegionTag })
|
|
var index, store []byte
|
|
for _, tag := range tags {
|
|
entry := h.entries[tag]
|
|
if size := typeSizes[entry.typ]; size > 1 {
|
|
store = append(store, make([]byte, (size-len(store)%size)%size)...)
|
|
}
|
|
index = appendUint32s(index, tag, entry.typ, uint32(len(store)), entry.count)
|
|
store = append(store, entry.data...)
|
|
}
|
|
|
|
count := uint32(len(tags) + 1)
|
|
region := appendUint32s(nil, sigTagRegion, typeBin, uint32(len(store)), indexEntrySize)
|
|
store = appendUint32s(store, sigTagRegion, typeBin, -count*indexEntrySize, indexEntrySize)
|
|
out := appendUint32s(nil, headerMagic, 0, count, uint32(len(store)))
|
|
out = append(append(append(out, region...), index...), store...)
|
|
return append(out, make([]byte, signaturePadding(len(store)))...)
|
|
}
|
|
|
|
func appendUint32s(buf []byte, values ...uint32) []byte {
|
|
for _, value := range values {
|
|
buf = binary.BigEndian.AppendUint32(buf, value)
|
|
}
|
|
return buf
|
|
}
|