Two bugs were found and patched in MP-SPDZ.
Bug 1 — Missing MAC check in multi-threaded POpen
(commit 5e714b2). The
SubProcessor<T>::POpen() function opens secret values. The MAC verification call
check() was only triggered by an explicit output-gate condition (inst.get_n()), so
in multi-threaded programs, some opened values could be used without the MAC checks
needed around the open:
1// FILE: Processor/Processor.hpp — MP-SPDZ (vulnerable, prior to fix)
2template <class T>
3void SubProcessor<T>::POpen(const Instruction& inst)
4{
5 if (inst.get_n())
6 check(); // ← MAC check only before the loop, only if inst.get_n() is truthy
7 // ... batched open setup ...
8 for (auto it = reg.begin(); it < reg.end(); it += 2)
9 for (int i = 0; i < size; i++)
10 C[*it + i] = MC.finalize_open();
11 // ← no MAC check after the loop, even when nthreads > 0
12}
The fix widens the pre-loop gate and adds a new post-loop MAC check with the
same gate, so multi-threaded opens trigger both checks:
1// FILE: Processor/Processor.hpp — MP-SPDZ (fixed, commit 5e714b2)
2template <class T>
3void SubProcessor<T>::POpen(const Instruction& inst)
4{
5 if (inst.get_n() or BaseMachine::s().nthreads > 0)
6 check(); // ← gate widened to also fire under multi-threading
7 // ... batched open setup ...
8 for (auto it = reg.begin(); it < reg.end(); it += 2)
9 for (int i = 0; i < size; i++)
10 C[*it + i] = MC.finalize_open();
11 if (inst.get_n() or BaseMachine::s().nthreads > 0)
12 check(); // ← NEW: post-loop MAC check, same gate
13}
Bug 2 — Race condition in Commit_And_Open_
(commit b86f29b). Inside
Tools/Subroutines.cpp, a shared coordinator object lets one thread signal to the
others that its commitment phase is complete. That signal was raised before the
commitment-opening validation loop ran, so a second thread waiting on the coordinator
could observe the “finished” state and proceed with values that had not yet been
verified:
1// FILE: Tools/Subroutines.cpp — MP-SPDZ (vulnerable)
2
3P.Broadcast_Receive(Open_data);
4coordinator.finished(); // ← signals completion before verifying
5
6for (int i = 0; i < P.num_players(); i++)
7 if (!Open(datas[i], Comm_data[i], Open_data[i], i))
8 throw invalid_commitment();
The fix moves the signal to after the validation loop:
1// FILE: Tools/Subroutines.cpp — MP-SPDZ (fixed)
2
3P.Broadcast_Receive(Open_data);
4for (int i = 0; i < P.num_players(); i++)
5 if (!Open(datas[i], Comm_data[i], Open_data[i], i))
6 throw invalid_commitment();
7
8coordinator.finished(); // ← now after verifying
The attack exploits the race by having a malicious party controlling Thread B observe
that Thread A’s coordinator has finished and immediately proceed to use the opened
values in its own MAC check instance, before A has confirmed those values are
authenticated. By carefully timing two concurrent MAC check instances the adversary
extracts information about $\alpha$ through the unauthenticated intermediate state,
then uses this to forge MACs on arbitrary output values.