CodeQL documentation

Writable file handle closed without error handling

ID: go/unhandled-writable-file-close
Kind: path-problem
Security severity: 
Severity: warning
Precision: high
Tags:
   - maintainability
   - correctness
   - call
   - defer
Query suites:
   - go-security-and-quality.qls

Click to see the query in the CodeQL repository

Data written to a file handle may not immediately be flushed to the underlying storage medium by the operating system. Often, the data may be cached in memory until the handle is closed, or until a later point after that. Only calling os.File.Sync gives a reasonable guarantee that the data is flushed. Therefore, write errors may not occur until os.File.Close or os.File.Sync are called. If either is called and any errors returned by them are discarded, then the program may be unaware that data loss occurred.

Recommendation

Always check whether os.File.Close returned an error and handle it appropriately.

Example

In this first example, two calls to os.File.Close are made with the intention of closing the file after all work on it has been done or if writing to it fails. However, while errors that may arise during the call to os.File.WriteString are handled, any errors arising from the calls to os.File.Close are discarded silently:

package main

import (
	"os"
)

func example() error {
	f, err := os.OpenFile("file.txt", os.O_WRONLY|os.O_CREATE, 0666)

	if err != nil {
		return err
	}

	if _, err := f.WriteString("Hello"); err != nil {
		f.Close()
		return err
	}

	f.Close()

	return nil
}

In the second example, a call to os.File.Close is deferred in order to accomplish the same behaviour as in the first example. However, while errors that may arise during the call to os.File.WriteString are handled, any errors arising from os.File.Close are again discarded silently:

package main

import (
	"os"
)

func example() error {
	f, err := os.OpenFile("file.txt", os.O_WRONLY|os.O_CREATE, 0666)

	if err != nil {
		return err
	}

	defer f.Close()

	if _, err := f.WriteString("Hello"); err != nil {
		return err
	}

	return nil
}

To correct this issue, handle errors arising from calls to os.File.Close explicitly:

package main

import (
	"os"
)

func example() (exampleError error) {
	f, err := os.OpenFile("file.txt", os.O_WRONLY|os.O_CREATE, 0666)

	if err != nil {
		return err
	}

	defer func() {
		// try to close the file; if an error occurs, set the error returned by `example`
		// to that error, but only if `WriteString` didn't already set it to something first
		if err := f.Close(); exampleError == nil && err != nil {
			exampleError = err
		}
	}()

	if _, err := f.WriteString("Hello"); err != nil {
		exampleError = err
	}

	return
}

References

  • The Go Programming Language Specification: Defer statements.

  • The Go Programming Language Specification: Errors.

  • © GitHub, Inc.
  • Terms
  • Privacy