Skip to content

azblob UploadBuffer AccessConditions silently dropped for files over 256 MiB #27031

Description

Bug Report

  • import path of package in question: github.com/Azure/azure-sdk-for-go/sdk/storage/azblob (and .../azblob/blockblob)
  • SDK version: github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
    • commit: 47b7a8a545174788f2bc750fdebda9bf4eb1e0ae (tag sdk/storage/azblob/v1.4.1)
    • also reproducible on main (see pointers below)
  • output of go version: go version go1.26.0 linux/amd64

What happened?

Client.UploadBuffer / Client.UploadFile accept an AccessConditions field via UploadBufferOptions / UploadFileOptions, but those access conditions are silently dropped when the payload exceeds MaxUploadBlobBytes (256 MiB) and the upload is performed as staged Put Block calls followed by a Put Block List commit.

As a result, a conditional create (IfNoneMatch: "*") is honored for payloads ≤ 256 MiB but not for payloads > 256 MiB — the existing blob is overwritten instead of the request failing.

Root cause is an asymmetry in blockblob/models.go. The single-shot path forwards AccessConditions, but the multi-block commit path used by UploadBuffer/UploadFile does not:

  • The size branch in uploadFromReader: ≤ MaxUploadBlobBytes → one Upload (Put Blob); otherwise stage blocks + CommitBlockList:
    if actualSize <= MaxUploadBlobBytes {

    commitBlockListOptions := o.getCommitBlockListOptions()
    resp, err := bb.CommitBlockList(ctx, blockIDList, commitBlockListOptions)
  • MaxUploadBlobBytes (256 MiB threshold):
    // MaxUploadBlobBytes indicates the maximum number of bytes that can be sent in a call to Upload.
    MaxUploadBlobBytes = 256 * 1024 * 1024 // 256MB
  • Single-shot options do carry AccessConditions (so the condition works ≤ 256 MiB):
    func (o *uploadFromReaderOptions) getUploadBlockBlobOptions() *UploadOptions {
    return &UploadOptions{
    Tags: o.Tags,
    Metadata: o.Metadata,
    Tier: o.AccessTier,
    HTTPHeaders: o.HTTPHeaders,
    AccessConditions: o.AccessConditions,
    CPKInfo: o.CPKInfo,
    CPKScopeInfo: o.CPKScopeInfo,
    }
    }
  • The buguploadFromReaderOptions.getCommitBlockListOptions() builds CommitBlockListOptions with no AccessConditions field, so the entire AccessConditions (both ModifiedAccessConditions and LeaseAccessConditions) is discarded on the commit:
    func (o *uploadFromReaderOptions) getCommitBlockListOptions() *CommitBlockListOptions {
    return &CommitBlockListOptions{
    Tags: o.Tags,
    Metadata: o.Metadata,
    Tier: o.AccessTier,
    HTTPHeaders: o.HTTPHeaders,
    CPKInfo: o.CPKInfo,
    CPKScopeInfo: o.CPKScopeInfo,
    }
    }
  • For contrast, the sibling UploadStreamOptions.getCommitBlockListOptions() does forward AccessConditions:
    func (u *UploadStreamOptions) getCommitBlockListOptions() *CommitBlockListOptions {
    if u == nil {
    return nil
    }
    return &CommitBlockListOptions{
    Tags: u.Tags,
    Metadata: u.Metadata,
    Tier: u.AccessTier,
    HTTPHeaders: u.HTTPHeaders,
    CPKInfo: u.CPKInfo,
    CPKScopeInfo: u.CPKScopeInfo,
    AccessConditions: u.AccessConditions,
    }
    }

Still present on main: https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/storage/azblob/blockblob/models.go (uploadFromReaderOptions.getCommitBlockListOptions() still omits AccessConditions).

This is purely an SDK omission, not a service limitation — Put Block List supports conditional headers: https://learn.microsoft.com/en-us/rest/api/storageservices/put-block-list ("This operation also supports the use of conditional headers to commit the block list only if a specified condition is met.").

What did you expect or want to happen?

AccessConditions supplied to UploadBuffer/UploadFile should be applied to the final Put Block List commit — exactly as they already are for the single-shot Put Blob path and for UploadStream. With IfNoneMatch: "*", the second upload of an existing blob should fail (409 BlobAlreadyExists / 412 ConditionNotMet) and leave the blob unchanged, regardless of payload size. Either fix it, or document that high-level buffer/file uploads ignore access conditions above MaxUploadBlobBytes.

How can we reproduce it?

Upload the same blob twice with IfNoneMatch: "*". With a payload just over 256 MiB the second call succeeds and overwrites; with a payload ≤ 256 MiB it correctly fails.

package main

import (
    "bytes"
    "context"
    "fmt"

    "github.com/Azure/azure-sdk-for-go/sdk/azcore"
    "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
    "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
    "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
)

func main() {
    ctx := context.Background()
    cred, _ := azblob.NewSharedKeyCredential("<account>", "<key>")
    client, _ := azblob.NewClientWithSharedKeyCredential(
        "https://<account>.blob.core.windows.net", cred, nil)

    const container, name = "<container>", "conditional-create-test"

    star := azcore.ETagAny
    opts := &azblob.UploadBufferOptions{
        AccessConditions: &blob.AccessConditions{
            ModifiedAccessConditions: &blob.ModifiedAccessConditions{IfNoneMatch: &star},
        },
    }

    // 260 MiB -> exceeds MaxUploadBlobBytes -> staged Put Block + Put Block List.
    // Change to e.g. 1<<20 (1 MiB) and the second call correctly returns BlobAlreadyExists.
    payload := bytes.Repeat([]byte{'A'}, 260*1024*1024)

    _, err := client.UploadBuffer(ctx, container, name, payload, opts)
    fmt.Println("first :", err) // <nil> (created)

    _, err = client.UploadBuffer(ctx, container, name, payload, opts)
    fmt.Println("second:", err,
        "BlobAlreadyExists:", bloberror.HasCode(err, bloberror.BlobAlreadyExists))
    // EXPECTED: err = 409 BlobAlreadyExists / 412 ConditionNotMet (blob NOT overwritten)
    // ACTUAL  : err = <nil>, the >256 MiB blob is silently overwritten
}

Observed with a small (8 MiB, single-shot) vs. large (260 MiB, multi-block) payload, each uploaded twice with a flipped content marker and overwrite confirmed via GetProperties (ETag) + a ranged read of the live content.

Anything we should know about your environment?

  • Linux, go1.26.0, Shared Key credential, standard azblob.Client.
  • Threshold is blockblob.MaxUploadBlobBytes (256 MiB): payloads at or below it take the single-shot Put Blob path (condition honored); above it take the multi-block Put Block + Put Block List path (condition dropped).

Metadata

Metadata

Assignees

No one assigned

    Labels

    ClientThis issue points to a problem in the data-plane of the library.Service AttentionWorkflow: This issue is responsible by Azure service team.StorageStorage Service (Queues, Blobs, Files)customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK teamquestionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions