The Go Programming Language

This chapter contains language-specific recommendations for Go.

Memory Safety

Go provides memory safety, but only if the program is not executed in parallel (that is, GOMAXPROCS is not larger than 1). The reason is that interface values and slices consist of multiple words are not updated atomically. Another thread of execution can observe an inconsistent pairing between type information and stored value (for interfaces) or pointer and length (for slices), and such inconsistency can lead to a memory safety violation.

Code which does not run in parallel and does not use the unsafe package (or other packages which expose unsafe constructs) is memory-safe. For example, invalid casts and out-of-range subscripting cause panics at run time.

Keep in mind that finalization can introduce parallelism because finalizers are executed concurrently, potentially interleaved with the rest of the program.

Error Handling

Only a few common operations (such as pointer dereference, integer division, array subscripting) trigger exceptions in Go, called panics. Most interfaces in the standard library use a separate return value of type error to signal error.

Not checking error return values can lead to incorrect operation and data loss (especially in the case of writes, using interfaces such as io.Writer).

The correct way to check error return values depends on the function or method being called. In the majority of cases, the first step after calling a function should be an error check against the nil value, handling any encountered error. See Regular error handling in Go for details.

Örnek 1. Regular error handling in Go
type Processor interface {
	Process(buf []byte) (message string, err error)
}

type ErrorHandler interface {
	Handle(err error)
}

func RegularError(buf []byte, processor Processor,
	handler ErrorHandler) (message string, err error) {
	message, err = processor.Process(buf)
	if err != nil {
		handler.Handle(err)
		return "", err
	}
	return
}

However, with io.Reader, io.ReaderAt and related interfaces, it is necessary to check for a non-zero number of read bytes first, as shown in Read error handling in Go. If this pattern is not followed, data loss may occur. This is due to the fact that the io.Reader interface permits returning both data and an error at the same time.

Örnek 2. Read error handling in Go
func IOError(r io.Reader, buf []byte, processor Processor,
	handler ErrorHandler) (message string, err error) {
	n, err := r.Read(buf)
	// First check for available data.
	if n > 0 {
		message, err = processor.Process(buf[0:n])
		// Regular error handling.
		if err != nil {
			handler.Handle(err)
			return "", err
		}
	}
	// Then handle any error.
	if err != nil {
		handler.Handle(err)
		return "", err
	}
	return
}

Garbage Collector

Older Go releases (before Go 1.3) use a conservative garbage collector without blacklisting. This means that data blobs can cause retention of unrelated data structures because the data is conservatively interpreted as pointers. This phenomenon can be triggered accidentally on 32-bit architectures and is more likely to occur if the heap grows larger. On 64-bit architectures, it may be possible to trigger it deliberately—it is unlikely to occur spontaneously.

Marshaling and Unmarshaling

Several packages in the encoding hierarchy provide support for serialization and deserialization. The usual caveats apply (see Serialization and Deserialization).

As an additional precaution, the Unmarshal and Decode functions should only be used with fresh values in the interface{} argument. This is due to the way defaults for missing values are implemented: During deserialization, missing value do not result in an error, but the original value is preserved. Using a fresh value (with suitable default values if necessary) ensures that data from a previous deserialization operation does not leak into the current one. This is especially relevant when structs are deserialized.

Good Practices for Securing Go - High Level Overview

Secure coding is the practice of writing programs that are resistant to attack by malicious or mischievous people or programs.

Golang’s adoption has been increasing over the years. Projects within Red Hat like Operators and Terraform have been completed in this programming language.

1. Use Go Modules

Modules are how Go manages dependencies. Go Modules allow for dependency version pinning, including transitive modules, and also provides assurance against unexpected module mutation via the go.sum checksum database.

For an introduction to creating Go projects, see How to Write Go Code. For information on using modules, migrating projects to modules, and other topics, see the blog series starting with Using Go Modules.

2. Validate input entries

Helps avoid attackers who send us intrusive data that could damage the system.

To validate user input, you can use native Go packages like strconv to handle string conversions to other data types. Go also has support for regular expressions with regexp for complex validations. Even though Go’s preference is to use native libraries, there are third-party packages like validator. With validator, you can include validations for structs or individual fields more easily.

3. Use HTML templates

One critical and common vulnerability is cross-site scripting or XSS. This exploit consists basically of the attacker being able to inject malicious code into the app to modify the output.

Go has the package html/template to encode what the app will return to the user. So, instead of the browser executing an input like <script>alert(‘You’ve Been Hacked!’);</script>, popping up an alert message; you could encode the input, and the app will treat the input as a typical HTML code printed in the browser.

There are also third-party libraries you can use when developing web apps in Go. For example, there’s Gorilla web toolkit, which includes libraries to help developers to do things like encoding authentication cookie values. There’s also nosurf, which is an HTTP package that helps with the prevention of cross-site request forgery (CSRF).

name := r.FormValue("name")
template := template.Must(template.ParseGlob("xss.html"))
data["Name"] = name
err := template.ExecuteTemplate(w, name, data)

4. Protect yourself from SQL injections

The first thing you need you to do is make sure a user that connects to the database has limited permissions. A good practice is to also sanitize the user’s input, as I described in a previous section, or to escape special characters and use HTMLEscapeString function from the HTML template package. But, the most critical piece of code you’d need to include is the use of parameterized queries. In Go, you don’t prepare a statement in a connection; you prepare it on the DB. Here’s an example of how to use parameterized queries:

customerName := r.URL.Query().Get("name")
db.Exec("UPDATE creditcards SET name=? WHERE customerId=?", customerName, 233, 90)

If using the db.Query() function instead, ensure you sanitize the user’s input first, as above.

5. Encrypt sensitive information

Go package that includes robust implementations to encrypt information like crypto.

6. Enforce HTTPS communication

To secure in-transit connection in the system isn’t only about the app listening in port 443. You also need to use proper certificates and enforce HTTPS to avoid attackers downgrading the protocol to HTTP.

w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")

You might also want to specify the server name in the TLS configuration, like this:

config := &tls.Config{ServerName: "yourSiteOrServiceName"}

Of Note: It’s always a good practice to implement in-transit encryption even if your application is only for internal communication. Imagine if, for some reason, an attacker could sniff your internal traffic. Whenever you can, it’s always best to raise the difficulty bar for possible future attackers.

7. Use caution with unsafe and cgo

Package unsafe provides an escape hatch from Go’s type system, enabling interactions with low-level and system call APIs, in a manner similar to C programs. However, unsafe has several rules which must be followed in order to perform these interactions in a sane way. It’s easy to make subtle mistakes when writing unsafe code, but these mistakes can often be avoided. This blog post: Safe-use-of-unsafe-pointer will introduce some of the current and upcoming Go tooling that can verify safe usage of the unsafe. Pointer type in your Go programs. If you have not worked with unsafe before, Recommended reading previous Gopher Academy Advent series blog on the topic.

Go’s cgo system for calling C functions offers a very convenient feature. As outlined in https://relistan.com/cgo-when-and-when-not-to-use-it

Here are some problems with using Cgo in your application:

  • It breaks a lot of Go’s awesome tooling

  • Puts Go’s concurrency promise at risk

  • Might break your static binary,

  • Breaks cross-compiling almost always

  • Calls into Cgo are much slower than native Go calls

8. Be mindful with Errors and Logs

Go doesn’t have exceptions. This means that you’d need to handle errors differently than with other languages. The standard looks like this:

if err != nil {
    // handle the error
}

Also, Go offers a native library to work with logs. The most simple code is like this:

package main

import (
	"log"
)

func main() {
	log.Print("Logging in Go!")
}

Finally, make sure you apply all the previous rules like encryption and sanitization of the data you put into the logs.