Null characters in Postgres: workarounds aren’t good enough
commandprompt.com
commandprompt.com
The pattern I have seen the most is logs. More often than not logs may embed binary chunks, which may contain the infamous null character. However, the whole log itself is a string. For example, this happens on web server logs (binary data tends to be attack attempts).
Because (obviously) this doesn't happen on PostgreSQL databases, I have experienced this while migrating from either Oracle or MongoDB to PostgreSQL. Nulls in both. One on logs, the other one as part of a user name (!!!). But still there.
PostgreSQL must support this. It is long due and required, specially to help database migrations. The author is clearly on point.
I actually raised this topic in 2016: https://www.postgresql.org/message-id/5aa1df8a-96f5-1d14-46f... But the conclusion was on the lines of "it would be too complicated, closing as WON'T DO".
Surely you can store anything on a bytea. However, that won't take you very far if you want to query the logs (partially or completely) from the database --which is indeed extremely useful.
In any case, Postgres always strived for compliance. And here, it is not accepting a UTF-8 valid character, hence is being non-compliant.
Escaping NULs wasn't the only reason we chose that encoding. UTF-8b correctly roundtrips not-quite-UTF8 data instead of erroring out. That's useful when you don't have full control over the data generation process, or expect rare corruption (e.g., in log data or catastrophic error reports).
A UTF-8 column type is not just a storage choice, it's also a constraint on your data... if it doesn't make sense to forbid NULs, does it make sense to demand that the data be well-formed UTF-8?
If you want to carry any valid UTF-8 string, you can as well treat them as binary blobs and solve the issue.
Unicode is as complex as UTF-8 encoding is simple an neat.
Joel Spolsky studied Unicode and was so sure that he understood it that he wrote a short intro for programmers without knowing that code points in Unicode don't correspond to "platonic ideal letters" aka user perceived characters. They correspond only in every case he used them (user-perceived character may actually be a sequence of code points).
There is the benefit of consistency. This is ostensibly, the issue at hand.
I see a big risk of opening yourself up to all kinds of bugs with this, for no apparent benefit. The article mentions null byte injection for example.
I can support that line of thought if we want to talk, for instance, about why Rust doesn't provide a "proper" iteration over characters [1] in their standard library. But in this case the Null character is an artificial construct that does not naturally exist in any language on Earth.
I sincerely doubt someone will ever find a naturally-ocurring "John \0 Doe". What I don't doubt is that such a name occurrs in a badly-migrated database somewhere, but that's a different discussion.
[1] https://doc.rust-lang.org/std/primitive.str.html#method.char...
I found it extremely annoying at first cause I was trying to copy/paste the stream chunks around and it wouldn't copy anything after the fist null. Then I realized this was probably a security hack in the hopes that people couldn't copy the data around (I can't think of any other reason to add these nulls like this otherwise). Funny enough, I opened the PDF in chrome and copy/paste of the selected text works fine. So clearly some readers strip these bad characters, but I can imagine others might not.
That sounds more like you're looking at UTF-16 data and trying to interpret it as ASCII.
BT 0 Tr 0.000000 w ET BT 44.814370 775.487087 Td [(\0S\0e\0r\0v\0i\0c\0e)] TJ ETOne oddity is that the PDF spec's description of the "Text String Type", e.g. at p.158 in https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdf_ref..., appears to say there should be a leading BOM (U+FEFF) in the string here, but this doesn't seem to be the case in reality. Indeed, adding one may cause issues for at least some PDF readers, according to discussion at https://github.com/tesseract-ocr/tesseract/issues/1150.
But anyhow, in short: that's simply UTF-16BE text being represented as a series of bytes. It's nothing to do with any kind of "security hack", and the null bytes are not "bad characters", they're the high byte of each UTF-16 code unit.
- Valid Unicode strings may contain null-bytes characters.
- valid json files may carry null-bytes as part of important and meaningful data
But… is there any use for a null byte explicitly in a JSON string? The de facto standard (easy to use and instantly recognisable) for blobs seems to be base64. I can’t think of any meaningful data benefits other than data efficiency (which is easily mitigated by storing the blob directly in the database).
If you use JSON correctly, a JSON string is really just an UTF8 string. Leaving out the null bytes there would be annoying, yes, but usually doesn't hurt the use as a string...
So just "leaving out the null bytes" amounts to changing the string to a different string. Sometimes that may be what you want, but it doesn't sound like a good idea in general.
It's really only the C standard that (ab)uses NUL as an end-of-string marker. So yes, after consideration, I'm starting to agree with you. U+0000 is a valid character and should be allowed in text fields.
The reasons are multiple: its faster to use write() than printf() (and you don't have to flush to synchronize), its way harder to exploit, and easier to move around in different buffers. And i got to wrote my own string library during my project, and that was fun.
And for what benefit? The only argument I've seen is "UTF8 doesn't forbid it". But that is quite weak, UTF8 doesn't prescribe it either, and for the enduser the null character has no meaning or representation that is any use, beyond end-of-string. UTF8 absolutely works fine without null characters.
And if you really want arbitrary binary data, use a blob, that's what thats for.
Don't what? The author isn't telling people to put NUL characters in their data; they're telling Postgres not to abort in the presence of a NUL character.
The main job of a database is to store and retrieve data. Erroring-out for valid UTF-8 strings can itself "wreak all kinds of havoc on applications", since it may be unexpected behaviour.
Personally, my expectation would be for a unicode string type to store any unicode string; and for data to be retrieved without modification. I would expect the same for any "generic plumbing" technology, whether it's a database, a programming language, a file system, a data processing command or API, etc.
As a side note, I tend to use property-based testing, which is especially good at ensuring NUL characters are handled correctly (property-based testing uses randomly generated inputs, starting with "small" values, and "shrinking" counterexamples; NUL is considered the "smallest" character, so it appears quite often).
And yes, they do wreak havoc, but not more than ascii strings containing null characters.
https://en.wiktionary.org/wiki/de-
Hence “to defraud” doesn’t mean removing fraud.