Favorite Bugs: errno and Idempotence

I love bugs. Well, some bugs. Some bugs are so insidious, or their solution so simple, that they're worth remembering. This is one of those bugs.

Recently I wrote a type in Swift to represent a SHA-256 hash. To generate hashes from data in iOS, you can use the CommonCrypto library, but there's no function that I'm aware of that will parse a checksum from a hexadecimal string representation. I wanted a nice Swift wrapper that could perform that translation and hide the internal, byte-array nature of the checksum from callers.

Thus, my initial (simplified) implementation of SHA256Checksum:

public struct SHA256Checksum {

    fileprivate let value: [UInt8]

    public init?(hexString: String) {
        guard hexString.utf8.count == Int(CC_SHA256_DIGEST_LENGTH * 2) else {
            return nil
        }
        var valueBytes = [UInt8]()
        for i in stride(from: 0, to: hexString.characters.count, by: 2) {
            let startIndex = hexString.index(hexString.startIndex, offsetBy: i)
            let endIndex = hexString.index(startIndex, offsetBy: 2)
            let byteString = hexString.substring(with: startIndex..<endIndex)
            let value = strtoul(byteString, nil, 16)
            if value == 0 && errno == EINVAL { // conversion failed
                return nil
            }
            valueBytes.append(UInt8(value))
        }
        self.value = valueBytes
    }

}

The core loop in the initializer uses the C function strtoul to convert a two-character hexadecimal string to an unsigned long. That function is documented as such:

If no conversion could be performed, 0 is returned and the global variable errno is set to EINVAL.

So, naturally, the code checks for those conditions and returns nil in such cases. Simple enough, right? I wrote some unit tests, and everything worked as expected. Ship it!

Some time later, this code started failing to parse valid hashes. Specifically, it would fail on 115b1577b7b517405200445cfb2f8b5fe9a1ba3e7e68229ad927bd3cfbe135e2. Why would that happen?

Well, it turns out to be a case of missing idempotence. In the failing value, did you notice the pesky 00? Passing that string to strtoul, one would expect to get back 0 as a result. Since that would be a successful conversion, one would not expect errno to equal EINVAL, right? And yet, using the debugger, I was able to verify that this is exactly what was happening.

strtoul is like many C functions in that it sets errno as a side effect. Unfortunately, it's not guaranteed to do so except in the case of failure. Therefore, if you're expecting a specific value of errno after calling it, you need to set errno to a sane value before that. Otherwise, some other function (anywhere prior to your call) may have set it to something you didn't expect, like EINVAL!

The fix is ridiculously simple, but not obvious to the uninitiated:

    ...
    let byteString = hexString.substring(with: startIndex..<endIndex)
    errno = 0 // FIX: preset errno to avoid catastrophe
    let value = strtoul(byteString, nil, 16)
    if value == 0 && errno == EINVAL { // conversion failed
        return nil
    }
    valueBytes.append(UInt8(value))
    ...

Old C pros have known this for years. It's so common that it's part of CERT's secure C coding guidelines. It's a great example of why idempotence is important (and side effects are bad). And that's what makes this one of my favorite bugs.