[HN Gopher] FORCEDENTRY: Sandbox Escape
___________________________________________________________________
FORCEDENTRY: Sandbox Escape
Author : ivank
Score : 193 points
Date : 2022-03-31 16:05 UTC (6 hours ago)
(HTM) web link (googleprojectzero.blogspot.com)
(TXT) w3m dump (googleprojectzero.blogspot.com)
| [deleted]
| spansoa wrote:
| What if we had multiple sandboxes instead? Then we could make the
| rogue malware 'think' it has escaped by using a decoy
| environment, and then it has to break another sandbox. Having
| just one sandbox is a SPOF and once that's breached, it's fair
| game. (Good) security has always been about /layers/ of security.
| klabb3 wrote:
| Is layering always more secure? Layering may create more
| complexity in the system overall (usually bad for security) and
| spreads efforts thinner if you are maintaining both sandboxes.
| What's the difference between good and bad layering?
| saagarjha wrote:
| This wouldn't help, because malware developers have access to
| iOS and can see the implementation of multiple sandboxes.
| Thaxll wrote:
| That's where I think Rust could be a game changer, if all those
| deserialization libraries used in phone would be written in safe
| Rust it woudn't be an issue.
| throwra620 wrote:
| invokestatic wrote:
| As explained in the introduction, this escape used only logic
| bugs. So rust would still be affected
| ghayes wrote:
| From the first paragraph:
|
| > By sending a .gif iMessage attachment (which was really a
| PDF) NSO were able to remotely trigger a heap buffer overflow
| in the ImageIO JBIG2 decoder.
| greiskul wrote:
| Yes, this attack had multiple stages. First stage is to get
| something running, and to do that they do use a memory bug
| that Rust would prevent. The second stage is the sandbox
| escape that the post describes which is done with only
| logic bugs, so that would still be vunerable if written in
| Rust.
|
| While rewritting the world is not possible, all these
| attacks show that definitely new systems should probably
| not be written in memory unsafe languages.
| O5vYtytb wrote:
| They're referring to a previously mentioned exploit:
|
| > Late last year we published a writeup of the initial
| remote code execution stage of FORCEDENTRY, the zero-click
| iMessage exploit attributed by Citizen Lab to NSO.
|
| The sandbox escape only uses logic bugs:
|
| > In this post we'll take a look at that sandbox escape.
| It's notable for using only logic bugs.
| jedisct1 wrote:
| You didn't even read the blog post, did you?
| Thaxll wrote:
| I did not indeed.
| tialaramex wrote:
| Rather than Rust, the correct choice here was WUFFS because
| Apple's explicit purpose here was to Wrangle Untrusted File
| Formats Safely which is literally why WUFFS is called that.
|
| Unlike Rust, WUFFS isn't a general purpose language it can only
| express a subset of possible programs. For example, WUFFS can't
| express the program NSO wanted here ("Provide a way to escape
| from this sandbox") whereas of course in a general purpose
| language that would feel annoyingly restrictive. What if you
| _want_ to escape from the sandbox?
|
| WUFFS is exactly the right tool for, say, parsing a file you
| received to turn it into an image for display on the iPhone's
| screen.
|
| Except, of course, if your focus is on features at all costs.
| If you don't care about security then it sucks that WUFFS can't
| take a file and maybe overwrite the operating system or
| whatever.
| ForgotIdAgain wrote:
| In the introduction "In this post we'll take a look at that
| sandbox escape. It's notable for using only logic bugs". My
| understanding was that Rust only cover memory corruption type
| of bug.
| xxpor wrote:
| Notably, _no_ language will ever be able to catch pure logic
| errors by itself. The computer can 't and shouldn't try to
| divine what you meant. It'll only do what it's told.
|
| Now, there are certainly advantages different languages have
| in ease of writing tests and things like that. There's also
| formal verification. But unlike memory errors, it's
| impossible to know if you told the computer to do something
| you didn't intend.
| jandrese wrote:
| Indeed this is the Halting Problem.
| sassy_quat wrote:
| I guess that's the true Turing Test, can an artificial
| general intelligence determine the complexity level and
| relative risk level of infinite loops for an algorithm
| written for a Turing machine.
| not2b wrote:
| The halting problem doesn't prevent correctness proofs,
| it only means that you get a three-valued answer: proof,
| counterexample, or too complex to determine. "Too complex
| to determine" usually means that the code needs to be
| rewritten to have simpler structure.
|
| And of course the proof is only for those properties that
| you write down, and you could also have a bug in the spec
| for those properties.
| roca wrote:
| > Notably, no language will ever be able to catch pure
| logic errors by itself.
|
| It's true you'll never be able to catch all logic errors
| automatically, partly because you need full correctness
| specifications for that.
|
| But languages like Rust with powerful static type systems
| can catch lots more important logic errors than C, or C++
| --- e.g. using typestate (catches bugs with operations on
| values in incorrect states), affine types (catches bugs
| with operations on "dead" values), newtypes (e.g. lets you
| distinguish sanitized vs unsanitized strings), sum types
| (avoids bugs where "magic values" (e.g. errors) are treated
| as "normal values").
|
| Also modern languages like Swift, and Rust to a lesser
| extent, are treating integer overflow as an error instead
| of just allowing it (or worse, treating it as UB).
| roca wrote:
| This exploit chain illustrates how once you have obtained a
| memory corruption primitive it is very hard to prevent full
| compromise. This makes it more important than ever to limit
| the ability of attackers to acquire memory corruption
| primitives.
|
| The best hope for legacy C and C++ code right now is a
| combination of extensive fuzzing and dynamic analysis to
| detect as many memory corruption bugs as you can, plus
| mitigations in CPUs and compilers to make it harder to turn
| memory corruption bugs into malicious execution, plus
| sandboxing to limit the damage of malicious execution. This
| exploit chain demonstrates techniques to bypass that entire
| mitigation stage (and also shows that Apple severely bungled
| their sandbox design).
|
| This doesn't bode well for the mitigation approach. They add
| significant complexity to the silicon and software stack,
| which has a cost in performance, but also ultimately security
| --- at some point we will see these mitigations themselves
| contributing to attack vectors. In return they make
| exploitation a lot more difficult, but mitigation bypass
| techniques can often be packaged and reused. For example
| stack overflow techniques have been effectively mitigated but
| that doesn't matter to attackers these days, who are now very
| good at using heap overflow and UAF. Meanwhile those stack
| overflow mitigations (stack cookies etc) still have to remain
| in place, making our systems more complex and slower.
| tialaramex wrote:
| For this specific work, _any_ mitigation is much worse than
| just solving the problem correctly.
|
| The WUFFS code to do this sort of stuff (parse file data,
| turn it into an array of RGB pixel values) is not only
| inherently safe, it's also typically faster than you'd
| write in C or C++ because the safety gives programmers that
| fearlessness Rust talks about for concurrency. The language
| is going to catch you every single time you fall, so, you
| get completely comfortable doing ludicrous acrobatics
| knowing worst case is a compiler diagnostic or a unit test
| fails and try again. When you have a hidden array overflow
| in C++ it's Undefined Behaviour, when you have a hidden
| array overflow in (safe) Rust it's a runtime Panic, when
| you have a hidden array overflow in WUFFS that's not a
| valid WUFFS program, it _will not compile_ now it 's not so
| hidden any more.
|
| So you're right, this doesn't bode well for mitigation -
| the answer isn't "more complex and slower" but "use the
| correct tools".
| blintz wrote:
| Very cool that this is just from logic bugs! I wonder if we
| should as a rule assume that sandboxing that is not formally
| verified or battle tested over a really long time is unlikely to
| be free of bugs.
|
| What's the long-term solution for these kinds of problems? How
| can we get out of this tar pit? Of course in the short run we can
| be dilligent about updates and bug bounties etc, but how can we
| actually eliminate these kinds of errors in a 'complete' way?
| mwcampbell wrote:
| Not just any logic bug. I think the most succinct
| identification so far of the specific type of logic bug is in
| this comment (not mine):
| https://news.ycombinator.com/item?id=30871034
| 0x0 wrote:
| Scarily elegant.
| ssklash wrote:
| This. The "weird machine" they built in the original exploit is
| the single most impressive exploit I've ever seen.
| RL_Quine wrote:
| The knowledge of even what the scope of the attack surface is
| is incredible, beyond any other exploits that have ever been
| made public.
| etaioinshrdlu wrote:
| I heard that Apple had a very hard time even understanding
| the exploit when they rushed a bandaid-patch for it, leaving
| most of the holes unpatched for the time being.
| saagarjha wrote:
| This isn't the first weird machine exploit used in the
| wild, and the people who respond to these kinds of things
| are quite familiar with them.
| [deleted]
| mwcampbell wrote:
| The ostensibly domain-specific interpreter tucked away in
| NSFunctionExpression is interesting, and I think it would be
| reasonable for Apple to try to rid their platforms of all such
| interpreters (with the inevitable exception of JavaScriptCore
| exclusively for the web). But I think the most important part is
| this:
|
| > The expressive power of NSXPC just seems fundamentally ill-
| suited for use across sandbox boundaries, even though it was
| designed with exactly that in mind.
|
| It reminds me of the YAML deserialization vulnerabilities that
| plagued Rails. It's clearly necessary to ensure that any data
| received from an untrusted source is merely data, with no generic
| way of instantiating arbitrary classes.
| d4mi3n wrote:
| Agreed. I worked in closure for a hot minute and learned of a
| pretty nice solution they had to this called EDN (pronounced
| "eden"): https://github.com/edn-format/edn
|
| I suspect it was inspired by the whole "data is code"
| philosophy of lisp languages, but it seemed like a well thought
| out pattern for encoding and decoding data in relatively safe
| ways. It had a way of tagging fields to indicate that they
| required processing to derive the decoded value, e.g.
| #inst "1985-04-12T23:20:50.52Z"
|
| Would be interpreted as a Java DateTime object, but one could
| just as easily read the raw data without respecting those tags
| if one didn't trust the safety of the data being read.
|
| In effect the format split the work of parsing the data from
| decoding the data, which is a distinction I haven't seen in
| many other data encoding mechanisms.
| silvestrov wrote:
| > starting in OS X 10.5 (which would also be around the launch of
| iOS in 2007) NSFunctionExpressions were extended to allow
| arbitrary method invocations with the FUNCTION keyword:
| "FUNCTION('abc', 'stringByAppendingString', 'def')" => @"abcdef"
|
| This is _always_ a bad idea. If you want a callback, register it
| manually so only registered functions are available.
|
| Java serialization and Log4J made same style of security bug.
| mwcampbell wrote:
| The fact that the extension of NSFunctionExpression in Leopard
| and the launch of iOS happened around the same time is
| interesting to me. Clearly one side of Apple was continuing to
| do everything they could to make their platform more attractive
| to high-level application developers, continuing the focus on
| this area that went back to the early years of NeXT [1],
| unaware (or unconcerned) that another part of Apple would be
| using this same framework in a consumer device where any way of
| abusing all of this power could be weaponized.
|
| [1]: See in particular the part of the original NeXT
| presentation right after intermission, with demos of gluing
| together high-level components using Interface Builder.
| https://www.youtube.com/watch?v=92NNyd3m79I
| slaymaker1907 wrote:
| Racket allows for macro/exec, but it strictly controls the
| namespace for both of these things. The former mainly via
| hygienic macros and the latter via requiring exec to be given a
| namespace to execute in.
|
| I think allowing things like NSFunctionExpressions are ok, but
| you need to have an explicit namespace, and the default needs
| to be an empty namespace, not the current/full namespace.
| Having a default is a good idea because if you don't add one as
| an API designer, other people creating wrappers around your
| library will, so at least try to make the default safe. It
| sounds like that is somewhat similar to your idea of
| registering a function.
| saagarjha wrote:
| It's easy to just look at features and call them "obviously"
| bad but this feature provides an extensible interface that
| isn't much different from any other API in Objective-C that
| takes a selector, e.g. sortedArrayUsingSelector:. This is just
| how the language is. It is your job to restrict what can go
| into that function; in this case where the attacker controls
| the address space it doesn't matter anyways because an attacker
| will just create a calling primitive elsewhere. So this is
| really just a kneejerk reaction that doesn't meaningfully help
| security here, and removes a fairly useful API.
|
| Note that if you actually wanted to make the attack harder here
| I'd go after the NSPredicate evaluation instead, which you can
| see in the blog post is done without checking the sender,
| rather than going after what the predicate itself can do.
| [deleted]
| plorkyeran wrote:
| NSPredicate has from the very beginning been the moral
| equivalent of `eval()`, with all of the security implications
| that has. Sending arbitrary messages to arbitrary objects is
| just fundamentally the thing it does. The addition of
| FUNCTION() certainly made it easier to turn a security
| vulnerability into a useful attack, but if an attacker can
| control the format string passed to NSPredicate (or more
| generally the construction of a NSPredicate) you always had a
| security vulnerability.
| valleyer wrote:
| It's striking how often these PZ iOS exploits at some point come
| around to too-polymorphic use of object unarchiving. Other
| examples:
|
| https://googleprojectzero.blogspot.com/2020/01/remote-iphone...
|
| https://googleprojectzero.blogspot.com/2019/08/the-many-poss...
| londons_explore wrote:
| It would seem the solution is to require every 'unarchive'
| operation to provide a 'schema' which specifies exactly which
| classes will be used in the unarchive operation. That schema
| would be read-only and specific to the unarchive call site.
| valleyer wrote:
| You're sort of describing NSSecureCoding. Unfortunately, it's
| not used everywhere, and (because Cocoa relies heavily on
| internal class clusters) it still allows subclasses of the
| specified class.
___________________________________________________________________
(page generated 2022-03-31 23:00 UTC)