HNHacker News
TopNewBestAskShowJobs

nneonneo

13,964 karma · joined April 1, 2016

Human-Computer Interaction researcher, computer security geek and CTF player.

https://github.com/nneonneo https://robertxiao.ca

submissionscomments
nneonneo··on Open research, stronger privacy: Making Firefox users harder to fingerprint
I think the idea is that a given JavaScript variable/context would be “tainted” the moment it accesses those APIs; taint tracking would then be used to guarantee that tainted data couldn’t be attached to any network request. The idea here is that fingerprint-capable APIs could be used but only locally.

There’s some parallels to canvas tainting: JS is blocked from reading the pixels of any canvas that has had a cross-origin image rendered to it. This is done to enforce the same-origin policy while allowing canvases to use cross-origin images like the <img> tag can.

Unfortunately, when you can run arbitrary JS, even doing strictly local computations can likely be used to leak tainted data to a cooperating untainted context. Since you’d want the JS to still be able to do something useful with the tainted data, that resulting changes could be measured to leak the data. A more draconian measure could be to just block fetch() and similar APIs entirely after you call a fingerprinting API, but that would probably break too many sites.

nneonneo··on The Megadonors Who Have Poured $1.2B into the Midterms
https://archive.ph/hYaHF
nneonneo··on I went to Mexico to test-drive the cars Americans can’t buy
From TFA:

  Government subsidies in China — while 5 to 10 percent of Chinese automakers’ capital spending, estimated O’Donovan, compared to about 1.5 percent in Europe — are no longer the Chinese auto sector’s primary advantage. Chinese carmakers now enjoy deeper structural advantages from their enormous scale, dense supply chains, high factory utilization and automated production.
Several companies in China now tout their “dark factories” where they make or assemble many parts of their cars with no human labour at all. It’s likely that the larger Chinese companies are capable of undercutting European makers even without government subsidies or wage parity.
nneonneo··on StackOverflow now gets fewer than 1000 questions a month
SO turned into a place that heavily favoured their experienced answerers over new answerers and askers. Admittedly, I fell into the former camp: someone with some excess time on his hands and an itch to answer interesting questions - and like most of the experienced answerers, I ended up being very disinterested in answering homework questions, “lazy” questions, and the like. “Downvote and move on” was the motto.

In hindsight, one thing we could’be done - as a community - was leave easy questions to be answered by new answerers. There’s still a lot of careful design to consider here - how do you avoid “broken windows” for newbies (and search engines), how do you moderate posts that the experienced users don’t/won’t interact with, etc. On the other hand, it would get those questions answered, give new answerers a chance to gain experience (and points) without getting beaten to the punch by some 100k’er, and generally reduce that air of unfriendliness and elitism that eventually permeated the site.

In any case, for better or for worse, I guess we have LLMs now. The real loss is the deep expertise embodied by all the answerers on SO. While 90+% of questions can probably be answered now by an LLM, it was nice to have a place where those remaining few questions could get a real, deep answer.

nneonneo··on eMudhra to exit global SSL/TLS certificate business
p=84491346121303840713246978535231635954643146756451201037561748571580203988423

q=82392744353585332429460820191121154258120959554076291572887591391377445536703

n=6961473881062880999817189307002435362675293718841800458853975151946795609411283948376850761685158526939121778595276868395992041449207352173613540433589369

e=1305276352699290187465722995062956630501617572282837586035120340990024176764584449553695476096002966088824142338449519554315283866861919281309484272012046

d=4

assert n == p * q

assert pow(pow(123456789, e, n), d, n) == 123456789

(This doesn't work for all messages `m`, so I agree with what you've said - d=4 is not a good choice. But it can work.)

nneonneo··on eMudhra to exit global SSL/TLS certificate business
As long as they continue to support the surviving root cert, they are beholden to the root program and need to be accountable for this incident.

So far their responses to critical questions in the Bugzilla (https://bugzilla.mozilla.org/show_bug.cgi?id=2083449) have been rather weak and delayed.

nneonneo··on eMudhra to exit global SSL/TLS certificate business
“d=4” is a perfectly legal RSA private key. The corresponding public key would probably have a weird e (not 65537, the usual choice), but would otherwise be legal, and could have an arbitrarily large modulus n (thus appearing “secure”).

Note that p,q (the primes you’re thinking of) are not actually required for decryption. They enable you to calculate d, the decryption exponent, and also enable decryption-time optimizations (exponentiation with d_p and d_q followed by CRT recombination), but that’s optional. In fact, p,q can be computed from n, e and d.

nneonneo··on Qwen3-Coder writes less secure code when told it's for a US persona
1. Were these the only prompts tested, or are these selected comparisons? I note that prior work used other triggers, like Tibet, which are omitted here - were they not tested, or did they simply fail to show similar results?

2. This is an old model that isn’t very good at writing secure code in the first place; I’m not sure it’s an improvement to use a “westernized” model that still writes insecure code 50+% of the time. If this is a genuine issue, it should be demonstrable with later models and with models that are more secure-by-default.

nneonneo··on eMudhra intermediate CA compromise
> After Wayne's report of key recovery, I was able to recover the private keys for the impacted keys trivially. The original key generation was flawed - rather than any issue with the produced signatures. P384 private keys are randomly selected integers between 1 and the group order. The 8 impacted keys all used integers less than 256. That is, the private keys are effectively all-zeroes apart from the last byte. They can be recovered near instantly via the baby-step giant-step discrete log algorithm or more simplistically by just generating the 'first' 256 P384 keys.

Yeah that’s just oof. Not hard to imagine the kind of code error that could cause this, but hard to imagine that any serious vendor of cryptography hardware or software could make a mistake like this.

nneonneo··on A Visual Intro to Complex Numbers
You’ve asserted several times in this thread that this is “LLM slop”. Having read a decent amount of math content on the internet, I’m not sure I quite agree, so I’d like to hear why you think it’s LLM slop?

For one, this article has (charmingly?) terrible math rendering, rather reminiscent of someone copy-pasting random pictures of equations from the Internet and supplementing them with Word’s Equation Editor or something. An LLM would have just dumped a pile of LaTeX and called it a day. For another, I don’t sense any of the usual writing tics that are suggestive of LLM writing. I don’t claim either of those are remotely definitive, but my personal feeling is that this was largely hand-written and aimed at a very introductory level (i.e. not the sort of level that will benefit from knowing about Riemann surfaces).

nneonneo··on A Visual Intro to Complex Numbers
This article misses one of the most useful and deep representations for complex numbers: polar coordinates. There’s some bits in there about adding angles and such when multiplying, but it turns out these are “obvious” in polar notation.

A complex number can be expressed in Cartesian coordinates as (x,y) or x+yi. It can be expressed as polar coordinates as (r,theta). What’s the corresponding complex notation? Surprisingly, it’s r*e^(i theta), where the angle is taken in radians. It might seem really weird to see an exponential there, but it turns out that the exponential behaves like a trig function when given an imaginary exponent - to be precise, e^(i theta) = cos theta + i sin theta.

Now multiplication becomes easy: multiplying two polar-notation complex numbers (r1,t1) x (r2,t2) gives r1r2 e^(i (t1+t2)) following the usual rules of exponential arithmetic, i.e. (r1r1, t1+t2).

The complex exponential looks like a very strange notation at first because we’re all used to grade school exponents, but you can think of it as a function that takes an angle and produces a point on the unit circle. Feed it a continuous input, and it traces out an arc or a circle. sin and cos are merely the one-dimensional projections of this function.

Frequency becomes simply e^(i ft) where f is the frequency in radians and t is time - representing a point racing around a circle at a set speed. Multiplying two signals with different (complex) frequencies produces a new signal with the sum of their frequencies. With this notation, complex signals are the simple ones to understand - real signals are the “more complex” ones, as every real signal consists of a complex signal added to its conjugate.

nneonneo··on The case against JPEG XL
The problem is that JXL has a ridiculously versatile modular mode which enables high-complexity “prediction” computations. These were designed to encode reusable, custom predictors that could reduce the prediction error and thus the number of bits needed to encode the error. However, the predictors can be abused to perform very complex computations instead. The prime image is only 4kx2k, but encodes a very complex prediction algorithm that happens to generate prime numbers.

In principle, a decoder could refuse to process images with predictors above a certain complexity limit, but it’s hard to know how to set such limits accurately.

nneonneo··on The case against JPEG XL
Probably not; for PNG, the image size is declared in the header, so a decoder can decide immediately if it wants to decode the image or not. The output is bounded by the size of the image times the bit depth, and decompression runs in time proportional to output size. zlib bombs exist, but they don't affect png because a decoder can simply refuse to decompress past the size of the pixel buffer.
nneonneo··on The case against JPEG XL
Worth noting that this progressive AVIF, when viewed in Chrome, has only two states: a "base layer" which pops in around 8KB, and the full image which appears 270KB later. The comparison JXL continues to refine during most of the download, so it winds up looking substantially better than the AVIF at around 95KB loaded, and looks pretty good (almost as good as the full 280KB AVIF) by 175KB. I suspect that there are a lot of knobs to turn for both progressive modes, but this particular case could definitely be cherry-picked to favour either codec.
nneonneo··on The case against JPEG XL
That JPEG XL prime computation is a pretty ugly DoS. Just selecting it in Finder, with the preview pane open, maxed out every core on my Mac inside a QuickLookSatellite that also ate 4GB of RAM while doing so - for a good 15 seconds. Not bad for a 2KB picture. It seems like Apple did not set sane limits on their JXL previewer.

It is, however, an incredibly cool demo of what the format is capable of. I'm not completely sure if an image format should be that flexible, but I'm impressed nonetheless.

nneonneo··on .name Termination
It seems like the right thing they should do is discontinue new registrations but continue to honour existing ones (+ continuing to reserve any 2LD that has a 3LD registered on top). It’s a bit insane that they can decide to just terminate all existing 3LD registrations. One would hope that they’d at least continue to reserve the 2LDs for some period to avoid domain squatting, but this isn’t mentioned in the proposal and I doubt Verisign would graciously do so.
nneonneo··on .name Termination
Since neither smith.name nor the wildcard *.name appear in the Public Suffix List (https://publicsuffix.org/), browsers would likely allow any page on a *.smith.name domain to set cookies for .smith.name.

There was an effort to properly handle the .name 2LDs, but it was never resolved because there’s no easy way to tell a reserved 2LD (open for 3LD registrations only) apart from a normal 2LD on .name: https://github.com/publicsuffix/list/issues/2306

So yes, this TLD’s setup is in fact pretty insane.

nneonneo··on Tell HN: HN's IPv6 address is down
Thanks, good call. I posted this partly hoping that one of those status sites could add IPv6 testing as a feature, but that’s probably asking too much :)
nneonneo··on Creepy Crawlies
The kernel folks likely have a good profile on what page people trigger Anubis on (i.e. what page people hit first). From that they could make heuristics about what pages are likely to be useful deep links.

Keep in mind that Anubis will rarely inconvenience an actual user who visits the site often; it’s meant to keep out first-time scrapers trying to grab a few pages from their queue.

nneonneo··on Creepy Crawlies
I disagree. The kernel finds it effective - 66% of scrapers are turned away directly.

The scraper problem now is fleets of residential proxy devices - often things like smart TVs, phones, and browsers with some “proxy SDK” installed as part of an app’s monetization scheme. They make a couple of requests to a site - just enough to fly under the radar - and move on to a different site.

If each new site they hit forces them to solve a proof-of-work, that’s a meaningful dent in their scraping performance. Many of these boxes may not even have the spare CPU power to efficiently solve so many proofs of work - and anything that makes an owner notice their device is running slow is something that could meaningfully impede adoption of these SDKs, or force the operators to choose between minimizing performance impact or scraping more sites.

nneonneo··on Creepy Crawlies
I wonder if one solution here could be to turn up Anubis difficulty if the first page hit is not one of the obvious entry-points to cgit. It could even have a little hint that says something to the effect of “go visit the home page if this is taking too long”. (Better not to ban them entirely, in case people really did click on some random link e.g. in a news story or mailing list message).

Distributed scrapers are going to generally try and hit their assigned list of pages; it’s a bigger waste of time if they have to go off to visit other pages first to get the cookie challenge.

nneonneo··on Python's pre-declared constants are kinda weird
I made a CTF problem where `assert` was used as a critical safety check - and where "accidentally" running the program under -O (for speed!) resulted in a security vulnerability. A large fraction of the people who attempted the problem seemingly missed this bug.

I would not be surprised in the least if that pattern existed in the wild. In fact, it's quite common to see this in C/C++ codebases too: people will use assert() to check a security-relevant property, and then disable those checks in their release builds "because it can't happen".

nneonneo··on Python's pre-declared constants are kinda weird
The __debug__ constant is really weird - any block of code guarded with `if __debug__:` will be entirely omitted from the bytecode under PYTHONOPTIMIZE=1. This and `assert` are the only two examples of real “conditional compilation” in Python. This is also the reason why you cannot assign to __debug__: doing so would make it possible to invalidate the compiler’s assumption about `if __debug__:` statements.
nneonneo··on Hook, hold, harvest and hide: Meta's alleged strategy laid out in first week
Did they actually use this phrase in their internal comms? The one reference I can find - https://www.justice.gov/sites/default/files/atr/legacy/2006/... - says that Steven McGeady (then VP of Intel) testified that Paul Maritz (then an exec at Microsoft) told Intel that Microsoft's strategy was to "embrace, extend, extinguish". However, I can't see that corroborated by any actual internal communications.
nneonneo··on One night in Uzbekistan: Why was this one data point so influential?
Figure 1a (the leftmost subfigure in TFA’s lead image) shows the data for Uzbekistan from the DOSEv1 dataset (green), DOSEv2 dataset (red) and World Bank (black). The authors of the retracted study used the DOSEv2 dataset in order to model climate effects on the economy at a sub-national level, as opposed to the country-level analyses used in prior work. However, it looks like the DOSEv2 data was just bad for all 14 provinces in Uzbekistan (a 90% drop in GDP for all provinces in 2020!).

The typical correlation between weather and the economy is going to be fairly noisy across the dataset, but if you have 14 extra datapoints all saying there’s a catastrophic GDP crash in one year together with some coincidental weather effect, that’s going to bias the model hard. Notably, they also extrapolate losses forward all the way to 2100, so the effects of such a bias will compound.

nneonneo··on Getting silly with C, part and((int*)-8)[3]
Only the first one is fully standards-compliant C; the others all invoke some non-standard (but widely-accepted) features. (I believe empty unions - and their interpretation as being 0-sized - are non-standard in C, but I am not 100% sure.)
nneonneo··on Getting silly with C, part and((int*)-8)[3]
Some of the previous editions came with explainers, but this one doesn't, so here's what's going on:

`void main() void;`: Despite sort of looking like an independent declaration (which threw me for a loop initially), this is actually an old-style C function definition in disguise. This style of function definition looks like `int foo(a, b, c) int a; int b; int c; { return a * b + c; }`. Without the misleading spacing, the blog code becomes `void main() void; void; { puts("hello world"); }`, in which it is clear that this is an old-style function definition with two useless declarations attached (void;). This one is both standard and free of UB.

`int typedef[[]]$;`: This is a combination of a few features: (1) $ is allowed as a legal identifier by some C compilers, (2) [[]] is an empty C23 attribute specifier sequence (basically a standardized form of __attribute__), (3) && is both the familiar binary operator, and (as a GCC extension) a unary operator that takes the address of a label, and (4) `typedef` doesn't have to be the first keyword in a typedef definition. So, `int typedef[[]]$;` is simply `typedef int $;` - defining $ as a type alias for int. `int main($[[]]$)` says that `main` takes one argument - an `int` (typedef'd $) called `$`. `[[]]$:&&$&&$&&puts("hello world");` defines a label called `$`, then breaks down as `&&$ && $ && puts(...)` - take the address of the label `$`, logical AND the argument `$`, logical AND the result of `puts`. This one uses non-standard features ($ identifier, unary &&), but is free of UB.

`goto *puts("Hello world"), puts("Goodbye world"), exit;`: this is a computed goto statement that evaluates the comma-expression `puts("Hello world"), puts("Goodbye world"), exit`, which calls `puts` twice and produces the address of `exit` (implicit function-to-function-pointer conversion), using that as the goto target. So, in effect, it's two free calls to puts followed by `goto *&exit`. Note that this is UB, as it effectively tail-calls `exit(int)` with no arguments. This one uses the non-standard computed goto feature, and has UB, although it will probably work in practice.

`printf("Let's count: %d %d %d %d\n", i++, var[42], i++, i++);`: As far as I can tell, this is a bit of compiler weirdness (and is extremely UB). `var` is an array of empty unions, and takes up no space at all (although it will still have a defined address in the binary). `var[42]` is an empty union object and, at least on x86-64, takes up no space in a variadic argument list; it's therefore skipped over entirely when building the arguments to `printf`. This means that `printf` actually gets one fewer argument than expected; the missing argument just-so-happens to be a zero on the Compiler Explorer demo. Because this one is very UB (both because of the unsequenced `i++` and the incorrect argument list), expect the result to vary depending on the mood your compiler is in. I believe empty unions are non-standard, and this one is definitely UB; the result is very compiler- and system-dependent.

`(my_type)2 + 2`: `my_type` is a pointer to an anonymous empty union with `sizeof` 0, so `(my_type)2` effectively treats 2 as the base address to an array of 0-sized unions. Adding a number to a pointer is equivalent to taking an array address (i.e. `p + x == &p[x]`). Since the size of each element is zero, asking for the address of element 2 is still going to give the base address (2). Compare this with, for example, `(int *)2 + 2` (gives 10, assuming `int` is four bytes in size). Note though that there is again UB here, because the argument to `printf` is a pointer, but it's being printed using the `%d` specifier. This again uses the non-standard empty union, and it also invokes UB.

nneonneo··on Diátaxis
Something that Python gets right is that their entire docs site is versioned - select 2.7.18, for example, and you get the 2.7 docs right down to the tutorial. The Python development process is also very careful about keeping documentation up to date; the PEP process even requires an explicit "How to Teach This" section for proposals that add or change language features.
nneonneo··on China begins producing advanced chipmaking deep-ultraviolet lithography machines
For highly complex technologies that require tons of capex to get anywhere, Chinese firms are perfectly happy to depend on foreign companies if they are allowed to.

Chinese chipmakers weren't willing to use the first batch of domestic DUV machines because they simply weren't as good as foreign alternatives, and this made it doubly difficult for domestic machine manufacturers: they couldn't get revenue for selling machines, and they couldn't learn from their customers' experiences. The Chinese government, for example, is now apparently withholding Nvidia chips from being imported - despite the export ban being partially lifted from the US side - in order to incentivize local companies to use domestic NPUs instead.

Similarly, on a personal level, most of the (technical) folks I know here in China are using Claude/GPT models because they want to use the best models available. But, companies like Bytedance (Doubao) cannot use such models to serve their customers, and ordinary folks also cannot access these models without workarounds. As such, the market for domestic AI models is huge, which has uplifted a lot of the current AI model providers.

Ultimately, this creates a funny conundrum: sanctions mean that certain parts of the Chinese population can still access the "banned" tech - able to experiment with it and learn from it - while spurring domestic companies to seek domestic alternatives.

nneonneo··on RipGrep musl binaries occasionally segfault during very-large searches
The biggest problem I find with AI writing is that it overemphasizes everything - making absolutely everything it does sound incredibly important and critical. This is very, very exhausting for humans to read, and I would not be surprised if this lack of emphasis differentiation hurts AIs in the long run too -- emphasis being a useful signal for determining what is more important or novel.

Actual technical writers know what to emphasize and what not to, based on the expected audience; good ones will gently add hints to expand that audience without compromising technical accuracy or content.

Page 1 of 34Next →