Hacker News new | past | comments | ask | show | jobs | submit login

I wonder why they aren't using the approach others are taking.

That is, compile an array access a[i] into an array of size N as a[i&M] where M is one less than the next power of two larger than N.

The logical and is very fast; it can be applied everywhere without a big performance hit. And this removes almost all the attack surface, as the real vulnerability is when the attacker forces speculative access to go way outside the array to unrelated memory that probably even belongs to another process.

Edit: actually there is an even better masking approach the linux kernel is using -- see https://lwn.net/Articles/744287/




That is, compile an array access a[i] into an array of size N as a[i&M] where M is one less than the next power of two larger than N.

Doesn't that still permit speculatively getting an out-of-bound element, where the problem gets worse with larger arrays?

The following LWN article describes a more sophisticated masking approach by Alexei Starovoitov:

https://lwn.net/Articles/744287/

Of course, the same question still applies: why not use this cheaper masking approach. Are there known problems with it?


I don't think the compiler always knows N. This would then be something the programmer (and/or a library implementer) should provide.


You can't read other process's memory with Spectre. And your solution would only work for statically sized arrays. In that case though it seems like a reasonable thing to do. Not a full solution though.


Spectre can read from other processes! Meltdown can even read across privilege levels (userland to kernel). But within userland, spectre lets you read anything. See: https://isc.sans.edu/forums/diary/Meltdown+and+Spectre+clear...


No, that'd be like claiming buffer overflow attacks let you exploit any other process on the system. It's technically true-ish but misleading.

You can only read from a process that you have managed to compromise, and spectre is a new category of exploit vectors not an exploit in and of itself.

So you need a process that takes external inputs (you need some way to influence it), and then you need to figure out how that input can resolve to a viable exploit via spectre. If your input validation code is spectre-free (LFENCE, whatever) then you're probably good to go. You don't need everything in your app to be spectre free. In the blog's example, for instance, he just assumes that somehow an attacker can influence the input X. But if there's no way for an attacker to do that because X never comes from any externally-influenced thing then you didn't need any LFENCEs on it.

Ensuring that all your external-touching code is completely spectre-free is non-trivial, of course, but it's not like you can just arbitrarily exploit anything you want, and you don't need every array access to be LFENCE'd or mask'd.


IMHO it's worth mentioning that this discussion is about Spectre variant 1 (as is the original article). I.e. we should not write "Spectre" when we talk about a specific Spectre variant.

I'm mentioning this because (at least to my understanding) in Spectre variant 2 the entire address space of the victim process can be used to find the "gadget" i.e. an usable target for the indirect branch. This means that making only your input validation code "spectre-free" is not good enough for variant 2. (This is why e.g. OpenSSH recently started using the (Spectre variant 2!) retpoline compiler flags of GCC/LLVM if available. See this thread for details: https://lists.mindrot.org/pipermail/openssh-unix-dev/2018-Fe...)


True, but variant 2 isn't as gloomy as it sounds because there's 2 major challenges with it. The first is you need detailed knowledge of the binary you're targeting as well as it's memory layout. ASLR makes that challenging, to say the least. You then also need a side channel of some sort to observe the effects, such as shared memory.


Is this true though? I'm no spectre expert, but my layman understanding is that since you can poison branch predictors, you can direct the other process to execute a gadget that wouldn't normally be executed. This means you can conceivably direct the other process to perform speculative operations on inputs you control, even if those code paths would never touch those inputs during normal execution.

I suppose if you mitigate the branch predictor poisoning (retpoline perhaps?) then this is not a concern any more.


You can only influence which branch it takes, you can't force it to jump to an arbitrary place. And there's a limit to how far down that branch it will go, of course.

But consider the example function in the article:

   void victim_function_v01(size_t x) {
     if (x < array1_size) {
          temp &= array2[array1[x] * 512];
     }
   }
If you can't control x as the attacker you can't really get this to do anything useful no matter which way you manage to get the if to predict. Simply forcing the if to speculate one way or the other does not result in arbitrary memory reads. You need to force the if and control x.


> You can only influence which branch it takes, you can't force it to jump to an arbitrary place.

Assuming you've mitigated spectre v2, right?

If you are vulnerable to v2, I can take any other indirect branch in your program (which may appear after some "y" I can control as an input) and have it speculatively branch from there to this "temp &= ..." code, leaking the value of "y".

If you are not vulnerable to spectre v2, then I agree, the paths are much more limited and tied to speculative execution that is related to attacker-controlled values.


You can only read memory that is mapped in the address space of vulnerable processes with Spectre. Usually, that is only memory from the current process and maybe a small bit of shared memory (and code segments of shared libraries, but those don't contain any interesting secrets). So while you can read memory from other processes, those processes need to be vulnerable and you cannot read memory from processes that applied mitigation.


Is this true? I'm no spectre expert but I thought that if you could detect cache changes correctly, you could infer the value of memory that you did not have mapped (but the other process did) as long as the other process changes its cache-touching behavior based on the value of the memory it had access to.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: