Summary
At work, I need to use the Cylance PROTECT antivirus software on my Mac. It detects the code in Nix ≥2.1 that copies paths from remote Nix stores as being a vulnerability, but it’s a false positive. The path of least resistance here was to just patch Nix to avoid triggering the false positive. I’m posting my patches here in the hopes that it will show up on a web search if somebody else hits the same problem and is also looking for a quick fix.
Details
The Cylance PROTECT antivirus software for macOS seems to detect any program using coroutines as having a “stack pivot” vulnerability. These programs are using stack pivots, but that’s just how coroutines work. I’ve looked at the offending code pretty extensively at this point, and I’m pretty sure that there’s no vulnerability here. (Security team – this means you can ignore the email I’ve sent you about this previously, in which I gave a heads up just in case there was a legit vulnerability here. I’ve investigated and there isn’t.)
Cylance started triggering after 48662d151bdf4a38670897beacea9d1bd750376a, and the fix I came up with was to just partially revert that. That change reduced memory usage used when copying from a remote store, but I’m happy to eat the extra memory use when the alternative is having my Nix be clobbered by the antivirus every time I try to use it.
Patches
Following are patches for both stable and unstable Nix that fix this problem.
Stable Nix (2.1.3)
From d0bb58ee4301f3b6abeb8ece3dbd25d93d2cdec6 Mon Sep 17 00:00:00 2001
From: Alyssa Ross <hi@alyssa.is>
Date: Wed, 14 Nov 2018 11:33:11 +0000
Subject: [PATCH] Work around Cylance's broken stack pivot check
The Cylance PROTECT antivirus software for macOS seems to detect any
program using coroutines as having a "stack pivot" vulnerability. These
programs are using stack pivots, but that's just how coroutines work.
There's no vulnerability here.
But, sometimes the path of least resistance is to just keep Cylance
happy, so this patch works around Cylance's broken behaviour by
partially reverting 48662d151bdf4a38670897beacea9d1bd750376a.
---
src/libstore/store-api.cc | 33 +++++++++++++++++++++------------
1 file changed, 21 insertions(+), 12 deletions(-)
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 1f42097fccf..d5a29753f0f 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -586,6 +586,26 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
uint64_t total = 0;
+ auto progress = [&](size_t len) {
+ total += len;
+ act.progress(total, info->narSize);
+ };
+
+ struct MyStringSink : StringSink
+ {
+ typedef std::function<void(size_t)> Callback;
+ Callback callback;
+ MyStringSink(Callback callback) : callback(callback) { }
+ void operator () (const unsigned char * data, size_t len) override
+ {
+ StringSink::operator ()(data, len);
+ callback(len);
+ };
+ };
+
+ MyStringSink sink(progress);
+ srcStore->narFromPath({storePath}, sink);
+
// FIXME
#if 0
if (!info->narHash) {
@@ -602,18 +622,7 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
info = info2;
}
- auto source = sinkToSource([&](Sink & sink) {
- LambdaSink wrapperSink([&](const unsigned char * data, size_t len) {
- sink(data, len);
- total += len;
- act.progress(total, info->narSize);
- });
- srcStore->narFromPath({storePath}, wrapperSink);
- }, [&]() {
- throw EndOfFile("NAR for '%s' fetched from '%s' is incomplete", storePath, srcStore->getUri());
- });
-
- dstStore->addToStore(*info, *source, repair, checkSigs);
+ dstStore->addToStore(*info, sink.s, repair, checkSigs);
}
--
2.18.0
Unstable Nix (9dc9b64aadadd9f3b8277fb942b00dad1a753fd3)
From af4302bf599b6d217526182d65ec6adb73e29326 Mon Sep 17 00:00:00 2001
From: Alyssa Ross <hi@alyssa.is>
Date: Wed, 14 Nov 2018 11:33:11 +0000
Subject: [PATCH] Work around Cylance's broken stack pivot check
The Cylance PROTECT antivirus software for macOS seems to detect any
program using coroutines as having a "stack pivot" vulnerability. These
programs are using stack pivots, but that's just how coroutines work.
There's no vulnerability here.
But, sometimes the path of least resistance is to just keep Cylance
happy, so this patch works around Cylance's broken behaviour by
partially reverting 48662d151bdf4a38670897beacea9d1bd750376a.
---
src/libstore/store-api.cc | 33 +++++++++++++++++++++------------
1 file changed, 21 insertions(+), 12 deletions(-)
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index dc54c735fdb..aaa16d5a999 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -588,6 +588,26 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
uint64_t total = 0;
+ auto progress = [&](size_t len) {
+ total += len;
+ act.progress(total, info->narSize);
+ };
+
+ struct MyStringSink : StringSink
+ {
+ typedef std::function<void(size_t)> Callback;
+ Callback callback;
+ MyStringSink(Callback callback) : callback(callback) { }
+ void operator () (const unsigned char * data, size_t len) override
+ {
+ StringSink::operator ()(data, len);
+ callback(len);
+ };
+ };
+
+ MyStringSink sink(progress);
+ srcStore->narFromPath({storePath}, sink);
+
if (!info->narHash) {
StringSink sink;
srcStore->narFromPath({storePath}, sink);
@@ -608,18 +628,7 @@ void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
info = info2;
}
- auto source = sinkToSource([&](Sink & sink) {
- LambdaSink wrapperSink([&](const unsigned char * data, size_t len) {
- sink(data, len);
- total += len;
- act.progress(total, info->narSize);
- });
- srcStore->narFromPath({storePath}, wrapperSink);
- }, [&]() {
- throw EndOfFile("NAR for '%s' fetched from '%s' is incomplete", storePath, srcStore->getUri());
- });
-
- dstStore->addToStore(*info, *source, repair, checkSigs);
+ dstStore->addToStore(*info, sink.s, repair, checkSigs);
}
--
2.18.0