Learn how to develop secure dApps on the Internet Computer!
Join our community conversation this Wednesday, November 16th at 7:30 AM PT / 4:30 PM CET. We’ll dive into two aspects of security: storing confidential data by @roel-storms, and issues around inter-canister calls by @robin-kunzler. You’ll learn about interesting and important attack vectors like double spending or loss of confidential data that are relevant in practice, and we’ll discuss how to prevent attacks.
Discussion and questions in this thread are very welcome. We’re looking forward to the conversation!
As promised, we follow up the community conversation with an answer to the questions which were asked during the session. Don’t hesitate to ask additional questions.
The recording will be online soon and a link will be added here.
This does not apply to “notify”. As you can see in the source code of the CDK, notify does not return a future as the call function does. Internally, it does not register a callback either:
Question 2 by Saikat Das
Is there a way to put another await after the local balance deduction?
Essentially, what is the right way to do atomic calls across canisters?
(Was answered in the talk and Q&A)
Question 3 by Saikat Das
instead of a lock, we could also put the before balance in a variable and check it after to ensure, it has not changed across the boundary. And if it has, just drop the entire transaction?
(Also answered in the Q&A) Yes, it is possible to detect that a race condition has occurred. However, it will not always be possible to undo inter canister calls that made undesired state changes. In our example, in case of a race condition, we would have transferred ICP in the ledger canister twice. This canister would need an API that allows us to undo this change.
Usually it is easier to prevent the issue than to correct it. However, prevention means that only the first caller would be able to execute and the subsequent calls would receive an error. You could achieve higher throughput in some use cases where an interleaving of messages is not always an issue and it is hard to determine up front if it would be an issue. In that case, letting calls run in parallel could increase throughput and only require a rollback in exceptional cases.
Detecting an issue instead of preventing it could be more challenging in more complex code.
Question 4 by Matthew Harmon
do the canisters get the HTTP headers passed to them?
(was answered in the talk)
HTTP headers are not removed by the boundary nodes and are available to the canisters.
Question 5 by Matthew Harmon
On the subject of encryption, has anything been said about encryption and replay attacks?
Even in case replay attacks were possible on a platform level, you could develop the notes canister API in such a way that the calls are idempotent. For example, replaying the setting of the public key of a device would result in an error since the alias is already registered. Another example would be setting the symmetric encryption key twice would still result in the user intended key being set.
Question 6 by Matthew Harmon
The symmetric key is not derived from the public key, but is encrypted by?
That is correct. If I stated otherwise in the talk, that was a slip of the tongue.
Question 7 by Saikat Das
Are there any suggestions on how to rate limit calls being made to my canister by external callers?
Highly depends on use-case.
There is no load balancer in front of a canister that you can configure as a developer on the IC. Any rate limiting you can influence should be implemented in the canister.
Rate limiting in a canister will also consume cycles. But it may consume less than the actual operation.
The rate limiting strategy depends on the use case. Is this a dApp accessible to anonymous users or are you requiring authentication? In case you have authenticated your users, you could keep track of calls per user per minute and put a limit on that. Returning an error for each call exceeding the limit.
For anonymous users, you could present a captcha for certain operations or as of a certain limit being exceeded.
For expensive calls, you could require users to supply some ICP first as a way to deter attackers.
Question 8 by Timo Hanke
Do we already provide any libraries for this kind of encryption to make it easy for devs?
No, but this would be part of threshold key derivation.
Question 9 by Saikat Das
Any idea if we’re any closer to homomorphic encryption where the hardware takes care of this and devs don’t need to implement secure key exchange themselves?
As they say, the most secure code is the one that isn’t written
We are looking at fully-homomorphic encryption (FHE), but is not for the near future.
Secure hardware for replicas (AMD SEV-SNP) is actively being looked at, see forum. Hopefully soon, but doesn’t protect against savvy adversary with physical access to machine (see one-glitch attack).
vetKD will come with dev libraries for encryption/decryption
Question 10 by Matthew Harmon
curious about the possibilty of an attack using poorly formatted http headers
What do you mean exactly? We’d be interested if you see a concrete attack vector.
Updated Canister Development Security Best Practices on Inter-Canister Calls and Rollbacks
We completely rewrote this section to give guidance on what security bugs (e.g. double spending, TOCTOU, …) to avoid when using inter-canister calls. We also describe a Rust locking pattern that helps to avoid certain bugs. I’d recommend to read this if you write canister code. Feedback/ideas/discussion are very welcome!
Many thanks to @roel-storms@oggy Andy and @frederikrothenberger for the collaboration!