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.
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.
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.
Further Reading
-
https://tutorialedge.net/golang/secure-coding-in-go-input-validation/
-
https://snyk.io/blog/go-security-cheatsheet-for-go-developers/
-
https://hackernoon.com/security-considerations-in-golang-xo4y3ykk
-
https://blog.sqreen.com/top-6-security-best-practices-for-go/
-
https://cyral.com/blog/security-as-code-implementing-lint-and-gosec/
-
https://blog.trailofbits.com/2019/11/07/attacking-go-vr-ttps/
-
https://utcc.utoronto.ca/~cks/space/blog/programming/GoCgoErrorReturns
Want to help? Learn how to contribute to Fedora Docs ›