6 ms·
Null characters in Postgres: workarounds aren’t good enough
- PaulHoule 6y agoFunny I have been making a 'data assembler' for AVR8 program memory which serializes a graph of data structures and all strings and arrays start with thr length.
- eska 6y agoWhat's the argument for allowing null bytes in text? I think this wasn't argued for well enough in the article. 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.
- taneq 6y agoI think the argument was just “text is incredibly complex with tons of obscure rules once you accept not-English-Latin text, so it’s best not to assume anything and just go by the standard.”
- probably_wrong 6y agoWhile I agree with you that this is one of the arguments the author puts forward, it does little to convince me. 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.chars https://doc.rust-lang.org/std/primitive.str.html#method.char...
- gbear0 6y agoActually I can think of one place I know I've seen explicit null characters in text, and that's a Facebook Business Record pdf. If you download your FB data the text instructions of all the PDF raw contents streams actually have a null character between EVERY SINGLE CHARACTER! 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.
- jfk13 6y ago> the PDF raw contents streams actually have a null character between EVERY SINGLE CHARACTER That sounds more like you're looking at UTF-16 data and trying to interpret it as ASCII.
- gbear0 6y agoIt's only the text instructions that have this, not the rest of the text. ie one line of the content looks like this, where it's trying to write the text 'Service' BT 0 Tr 0.000000 w ET BT 44.814370 775.487087 Td [(\0S\0e\0r\0v\0i\0c\0e)] TJ ET
- jfk13 6y agoThis reflects the fact that PDF uses the UTF-16BE encoding form for Unicode text, not UTF-8. One 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_reference_archive/pdf_reference_1-7.pdf 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 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.
- wodenokoto 6y agoThe arguments in the text: - Valid Unicode strings may contain null-bytes characters. - valid json files may carry null-bytes as part of important and meaningful data
- yunruse 6y agoAs weird and inefficient as it may seem at first glance, JSON in a database can have a variety of uses, so I certainly see the benefit. 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).
- corty 6y agoJSON strings must conform to UTF8. Storing plain non-base64 blobs in them is abuse of broken JSON parsers.
- zaphar 6y agoThat second one is probably the more important one. A null byte in plain text is probably an error. A null byte in a json string may be intentional if perhaps questionable. JSON strings are frequently abused for various transport formats.
- eptcyka 6y agoIf I was responsible for ingesting and returning JSON strings from a server, my attidue towards people who require nulls in them would be rather juvenile and brash - "lmao fuck em".
- lolc 6y agoAnother reason mentioned is consistency with other RDBMS.
- corty 6y agojson carrying important and meaningful data in strings (i.e. blobs in strings) is usually just an exploit on the non-conformance of parsers. JSON strings are defined by ECMA-404 to be UTF8 codepoints. Arbitrary binary data isn't a sequence of UTF8 codepoints. However, that's what it usually is used for, incorrectly. 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...
- legulere 6y agoIt helps pressure other people to not use null-delimited string routines which are also prone to buffer overflow.
- chriswarbo 6y agoThe same as the argument for allowing BEL, DEL, BOM, and any other control codes or special characters: implementing a standard as described, with minimal divergence or unexpected behaviour, to ensure compatibility with arbitrary external software.
- tyingq 6y agoThere are lots of programming languages where the "string" type is fine with nulls anywhere in the "string". And other databases allow it. Perhaps they shouldn't, but it's a sort of defacto standard.
- corty 6y agoOh no please don't. Null characters in strings wreak all kinds of havoc on applications. Smuggling data in and out of somewhere because the validation only looks at the start of the string? Padding for fake checksums and signatures because half the string isn't shown and is free game? Length screwups because strlen tells something different from the actual size? Of course one could disregard that as problems with legacy code, but it ain't. Syscalls in most OSes only handle nullterminated strings. Same for a lot of network protocols where a null character is a terminator or separator. 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.
- prionassembly 6y agoHeads up, "proscribe" also means "forbid".
- Pxtl 6y agoHooray for English words where antonyms sound almost exactly the same. Prescribe/proscribe.
- pavlov 6y agoAlso fun is the “de” prefix which can mean either removal or its opposite, intensification: https://en.wiktionary.org/wiki/de- https://en.wiktionary.org/wiki/de- Hence “to defraud” doesn’t mean removing fraud.
- hanche 6y agoNot to mention “inflammable”, which means the same as “flammable”. Because the former is so easily misunderstood – and has been – warning labels now use the latter.
- 6y ago
- nabla9 6y agoUnicode standard has made bad choices over the years. "Because it's valid Unicode" is not a good reason to do anything. It's better to detect errors in strings sooner than later. If you want to carry any valid UTF-8 string, you can as well treat them as binary blobs and solve the issue.
- chriswarbo 6y agoCan I ask what the point of a "string" type would be, if it causes unexpected errors and should be avoided in favour of blobs?
- hamandcheese 6y agoIn the case of Postgres, you need string types if you intend to do any string operations on those strings in the database. As mentioned in the article, you also have no choice if you want to use jsonb.
- Yaggo 6y agoYeah. There's probably all kind of obscure use cases for ASCII, but generally, any text meant to be human readable should be Unicode encoded, whether you call it "string" or "blob".
- nabla9 6y agoNothing. If you accept only workable specific subset of Unicode and validate input, you can get the point back. Using utf-8 encoding for text is almost always the right ting to do, treating it as generic Unicode string is usually wrong. 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).
- Supermancho 6y ago> "Because it's valid Unicode" is not a good reason to do anything. There is the benefit of consistency. This is ostensibly, the issue at hand.
- ahachete 6y agoI have been bitten by this many times. Null character in strings, as weird as it sounds like, happens in real life. It just happens. 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-46fd-032e32846c71%408kdata.com 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".
- corty 6y agoA log containing binary chunks is not a string, because it will probably contain invalid characters, at least if your string encoding has the concept of invalid characters like UTF8 has. Since logs usually are not in any fixed encoding but in whatever the currently running process uses, even the string parts of your log are unlikely to always conform to UTF8. So blob would be the appropriate data type anyways.
- ahachete 6y agoLogs, unless specifically being told otherwise, are assumed to be in text format. Even if they may contain binary blobs (which are normally unexpected, and not really conformant), they are still supposed to be text format. 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.
- deleted 6y ago[deleted]
- nr2x 6y agoI have a large scale web analysis platform and the amount of effort I’ve had to put in to handling this is immense. You don’t appreciate how good browsers are at handling malformed code until you try to ingest tens of millions of pages and scripts. I switched to Postgres partially to get away from MySQL really poor handling of emoji, and got this bug in place.
- ape4 6y agoWe're going to need a long transition period where programs don't exception if they get a null in a string.
- pkhuong 6y agoI switched some in-house database written in C to a variant of UTF-8b where the NUL byte is also surrogate escaped, mostly because of pervasive NUL termination issues as mentioned in the OP. 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?
- formerly_proven 6y agoSurrogate escaping / UTF-8b is kinda genius. Unicode has two reserved ranges of 256 values each ("surrogates") reserved for the UTF-16 encoding, which are used there for encoding stuff outside the BMP. Surrogate escaping realizes that 1) These never appear in valid UTF-8, because UTF-8 is a variable-length integer, and doesn't need surrogates 2) So you can use them to pass-through binary data in UTF-8.
- CUViper 6y agoOn the range, the surrogates are actually 10 bits, 1024 possible values. The high surrogate is encoded in 0xD800-0xDBFF, and low in 0xDC00-0xDFFF.
- orwin 6y agoIn my last school year, after three month doing security CTF, and for all my C projects since, i started bringing with the string its length and did everything i could to avoid using \0. I think almost all non-deprecated C stream function give you the string length (or bits received rather). 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.