Is stack protection being forced on always?

Hi all,

In the process of catching up with Swift 5.11, I've rebased our compiler fork on top of modern swift and finally started running our unit test suite for our compiler IDE. Lots of our local tests are failing due to the missing symbol __stack_chk_guard. I'm new to stack protection and only just now noticed Erik's implementation. I can just add a global into all our programs for this but this feels like overhead we don't want on our platform possibly? Plus if it fails... we don't really have a concept of programs "trapping on safety exceptions" in our small embedded world.


I tried adding -Xfrontend -disable-stack-protector into our swift command line but I'm still getting the missing symbol. It might be I need to go back and add that into the compile of all our hardware libraries too.

I suppose I'm just looking for answers to these questions if anyone (@Erik_Eckstein?) can shed some light.

  1. am I understanding it right that over the last year, Swift has basically added stack protection on all normal (not inline) functions, at a brief glance this is implemented by asking llvm to add it to the function prolog/epilog?

  2. if I decide that we shouldn't have that on our platform, is the above flag "safe to use" and correct? Or will it have unintended consequences I haven't realised (apart from a lack of stack protection)?

  3. am I being over protective and we would be better to just "take the hit everywhere" and allow it? Our platform is super sensitive to code bloat, so the thought of adding anything to stack frames across all functions makes me feel a little queasy, but I might be over sensitive!

Thanks for any advice and help anyone can give.

Cheers,
Carl

3 Likes

@carlos42421 thank you very much for that post!

I faced with that error while building for Embedded Swift, then found your post and seems -Xfrontend -disable-stack-protector works on the latest swift-DEVELOPMENT-SNAPSHOT-2024-05-01-a snapshot.

Now I'm facing with undefined symbol: arc4random_buf error.
Is there a way to suppress it also or maybe I use something prohibited in Embedded Swift which causes it? Any help would be appreciated.

arc4random_buf() is the random byte generator used on Darwin, and Embedded Swift expects it to exist somehow. You can simply implement it yourself and wrap your platform's PRNG interface:

void arc4random_buf(void *buffer, size_t size) {
  myRNG(buffer, size);
}

If you do not use Hashable, Dictionary, Set, or any RNG facilities, you don't need to implement this function. If you only use Hashable/Dictionary/Set, but don't use any RNG facilities directly, you could implement it to simply call memset(buffer, 0, size) or some equivalent, and I believe it'll be "good enough" to satisfy Hashable's requirements.

For more info, see swift/docs/EmbeddedSwift/UserManual.md at main · apple/swift · GitHub

3 Likes

@kubamracek would it be possible to remove Hashable's dependence on random numbers when building for embedded? There's a very good chance that an embedded system is not going to have a high-quality RNG available.

1 Like

I wonder what @Philippe_Hausler thinks -- last time we talked about that, I think we were saying that the randomized hash seeding that Hashable uses is a useful property. But perhaps there could be some mechanism to opt out of that? That said, I don't know what that mechanism would be -- in userspace, there is an environment variable for that purpose, but envvars are almost certainly not something we'd want to rely on in embedded systems and also it wouldn't solve the dependency on arc4random_buf as it would be a runtime decision.

I believe the randomness is used to 'salt' the hashes in order to make things like enumeration order non-deterministic, to help avoid people unknowingly depending on it (it's an implementation detail and subject to change at any time, even without deliberate randomness).

So the question is: does that matter in "embedded" applications?

I would think it does - you still don't want to depend on unstable implementation details.

But perhaps the stdlib uses of arc4random_buf could have a reasonable fallback path for when it's not available at runtime, like just using a system clock (e.g. mach_absolute_time on Darwin)? Provided a clock is available on the target platform (almost all offer something sufficient for this, in my experience), and that clock has sufficient resolution, the exact moment at which the collections' salts are sampled is potentially variable enough to not get the exact same timestamp every time.

1 Like

There's that, but it's also a security mitigation; if an attacker does manage to find a set of keys that produce a hash collision under one seed, causing operations to degrade to O(n) and potentially DoS an application, then it is less likely they can replay that attack under a different seed.

8 Likes

So, I'm intentionally being a bit naïve here about the security implications, but if we are supposing we're using Embedded Swift on a platform, architecture, widget, whatever that does not have a high-quality PRNG, then there's not really much security benefit to seeding hashes because the only implementation available for a faux arc4random_buf() would either produce garbage or rely on a low-quality PRNG like K&R rand().

There are a couple of options here that I think would be palatable:

  1. Conditionalize the use of the PRNG in Hashable on some compiler conditional that could be set when it's unavailable (SWIFT_NO_RANDOMIZED_HASHES or some such)
  2. Using the current time as the initial seed value if the system supports any sort of high-precision clock. @wadetregaskis mentioned using mach_absolute_time() instead (although perhaps we could just use the nanoseconds field from timespec_get(), which is part of the C standard?)

I'm sure there are other options too that could work as a substitute for arc4random_buf().

I'm on vacation and replying from an iPhone so this won't be very complete, but...

We want the behavior of an embedded swift program to match that of any other swift program, barring features that are by-design incompatible with the embedded swift mode.

This means features like strings and arrays which are not ok for some embedded targets are available by default (matching desktop swift), but can be disabled with flags (WIP for those types).

Similarly, for disabling randomized hashing this would need to be a flag that applies to both embedded and desktop swift.

Lastly configuring the clock on embedded targets is often just as complicated as configuring their RNGs, requiring specific PLL, prescalar, and divider setups so all peripherals work correctly.

2 Likes

I'm by no means an infosec expert, but it's my understanding that any per-process perturbation at all makes this kind of attack much harder to exploit, even if it isn't cryptographic-quality randomness. That said, it would make sense to me to have a hook dedicated to generating the hash seed rather than have it rely on a generic randomness hook. arc4random definitely implies a higher-quality source of random than, say, reading noise off an ADC pin, even though the latter could still be better than nothing for hash seeding.

4 Likes

In particular, arc4random() needs to generate bulk randomness, while we only need a fairly small amount of randomness for salting hasher. It makes pretty good sense to be able to wire that up to something else.

4 Likes

We could expose a hook function. Spitballing…:

extern "C" std::atomic<uintptr_t (*)(void)> swift_getHashableSeed;

std::atomic<uintptr_t (*)(void)> swift_getHashableSeed = [] () -> uintptr_t {
#if defined(SWIFT_NO_HASHABLE_SEEDING) // or whatever
  return 0;
#elif defined(__APPLE__) // or BSD
  uintptr_t result;
  arc4random_buf(&result, sizeof(result));
  return result;
#elif defined(__linux__)
  // Or read from /dev as needed.
  uintptr_t result;
  (void)getrandom(&result, sizeof(result), 0);
  return result;
#elif defined(_WIN32)
  unsigned int lo;
  (void)rand_s(&lo);
  #if defined(_WIN64)
    unsigned int hi;
    (void)rand_s(&hi);
    return (static_cast<uintptr_t>(hi) << UINTPTR_C(sizeof(lo) * CHAR_BIT))
      | static_cast<uintptr_t>(lo);
  #else
    return static_cast<uintptr_t>(lo);
  #endif
#else
  // Low-quality RNG in use. Add platform-specific code here
  // or set swift_getHashableSeed early during execution.
  return rand();
#endif
};

Just a thought.

Carl

In a world where dynamic linking doesn't exist, a hook seems like overkill, since you can link in the implementation of the symbol you want at build time, or provide a default implementation as a weak definition that user code can override.

Makes sense. A weak symbol that returned something obvious like 1 by default for the hash seed would make sense. +1

On the other hand, a hook could be set by XCTest/swift-testing to allow more deterministic behaviour during test runs (subject to some :ballot_box_with_check: checkbox somewhere.)