Skip to content

File Uploads Fail addFile() Closes File Handle Before Reading #4153

@0xcro3dile

Description

@0xcro3dile

it is a simple typo in logic but its impact is catastrophic for the usability of the SDK. it renders a major capability of the library completely non functional. The addFile() function in all service clients contains a critical bug where the file handle is closed immediately after opening, and then the code attempts to read from the already closed file using io.Copy(). This causes all multipart file uploads to fail with the error:

read /path/to/file: file already closed

The bug is located in services/*/client.go (lines 499-516) and affects all 37+ service clients in the SDK since the code is auto generated.

The Buggy Code

func addFile(w *multipart.Writer, fieldName, path string) error {
    file, err := os.Open(filepath.Clean(path))
    if err != nil {
        return err
    }
    err = file.Close()  //  bug: File is closed here!
    if err != nil {
        return err
    }

    part, err := w.CreateFormFile(fieldName, filepath.Base(path))
    if err != nil {
        return err
    }
    _, err = io.Copy(part, file)  //  bug: trying to read from closed file!

    return err
}

Steps to reproduce

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "os"
    "path/filepath"
)

// This is the EXACT code from services/iaas/client.go (lines 499-516)
func addFile(w *multipart.Writer, fieldName, path string) error {
    file, err := os.Open(filepath.Clean(path))
    if err != nil {
        return err
    }
    err = file.Close() // bug: closed too early!
    if err != nil {
        return err
    }

    part, err := w.CreateFormFile(fieldName, filepath.Base(path))
    if err != nil {
        return err
    }
    _, err = io.Copy(part, file) // fails: file already closed

    return err
}

func main() {
    // Step 1: create a test file with content
    testFile, _ := os.CreateTemp("", "upload_test_*.txt")
    testFile.WriteString("This content should be uploaded to the API!")
    testFile.Close()
    defer os.Remove(testFile.Name())

    // Step 2: try to add the file to a multipart form (simulating SDK file upload)
    var buf bytes.Buffer
    writer := multipart.NewWriter(&buf)

    err := addFile(writer, "file", testFile.Name())
    writer.Close()

    // Step 3: observe the error
    if err != nil {
        fmt.Printf("ERROR: %v\n", err)
    } else {
        fmt.Println("Success (unexpected)")
    }
}
  1. run the code above with go run main.go
  2. observe the error output

Actual behavior

The SDK returns an error when trying to upload any file:

ERROR: read /tmp/upload_test_1234567890.txt: file already closed

The file upload operation fails completely. no data is sent to the API.

Test output from POC:

=== RUN   TestRealSDK_AddFile_Bug_FileClosedBeforeRead
 bug CONFIRMED: Real SDK addFile() returned error!
   Error: read /tmp/real_sdk_addfile_test_3259669529.txt: file already closed
   Confirmed: 'file already closed' error!
--- PASS: TestRealSDK_AddFile_Bug_FileClosedBeforeRead

Expected behavior

The addFile() function should:

  1. Open the file
  2. Read its contents using io.Copy()
  3. Then close the file after reading is complete

The file should remain open until after io.Copy() finishes reading. The correct implementation should use defer file.Close():

func addFile(w *multipart.Writer, fieldName, path string) error {
    file, err := os.Open(filepath.Clean(path))
    if err != nil {
        return err
    }
    defer file.Close()  //  close after io.Copy completes

    part, err := w.CreateFormFile(fieldName, filepath.Base(path))
    if err != nil {
        return err
    }
    _, err = io.Copy(part, file)  //  file is still open here

    return err
}

Environment

  • OS: Linux (Ubuntu 22.04)

  • Go version (see go version): 1.25.5

  • Version of the STACKIT Go SDK:


$ cat go.mod | grep "stackit"

module github.com/stackitcloud/stackit-sdk-go/services/iaas
	github.com/stackitcloud/stackit-sdk-go/core v0.20.1
	github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.18.2

Additional information

affected Services

This bug affects ALL service clients (37+ files) because the code is generated from the same template:

Root Cause

I think the bug appears to be in the OpenAPI Generator template used to generate the SDK client code. fixing the template would fix the bug across all services when the SDK is regenerated.

I'm happy to help test a fix or sumbit a PR . Thank you!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions