NNS Proposal: Make canister_status public to anyone

Fwiw Andreas’s posts are the ones that convinced me that a privacy-by-default approach may make sense in this case (with an option for developers to expose).

1 Like

Why does it worry you?

My bad, was going to write something else initially and forgot to delete that part :sweat_smile:

Hey everyone, I’d like to share some of the current thinking on this topic. Btw, thanks @Fulco for bringing this up in a constructive way.

First of all, let’s take a look at the result returned by canister_status (taken from the interface spec):

canister_status : (record {canister_id : canister_id}) -> (record {
      status : variant { running; stopping; stopped };
      settings: definite_canister_settings;
      module_hash: opt blob;
      memory_size: nat;
      cycles: nat;
      idle_cycles_burned_per_day: nat;
  });

In the result there are certain fields that are private to the canister and its controllers, like cycles, the rate of burning, memory size and settings. The reason I say these are private is because they can reveal information about the canister’s status and an attacker could even exploit some of them to learn patterns about the canister’s usage or even force specific cycle drain attacks.

I think it’s a slippery slope if we take an approach where such private fields become public by default: it would break potentially existing assumptions some canister developers have and (worse imo) can surprise many people that never even thought that such attacks could be possible. In a sense, I very much agree with the opinions others have voiced in this thread about keeping the privacy of these fields and potential issues if we change them to public.

That pretty much excludes options A and C imo right from the get go. Even if we believed that everything should be public, we cannot break existing applications which all they know so far is that this information is accessible to controllers – that would also be very bad user experience.

Between B and D: we have discussed this internally a lot I must say. We are currently planning to provide B (essentially give access to the information that’s already public like the controllers and wasm hash to anyone). The reason D should not be preferred imo is that it’s better for the system to not provide an option that can backfire in some cases (think of attacks that attempt to analyze your canister’s cycles usage) and leave the option to expose this completely to the application level (e.g. with the blackhole approach that Fulco mentioned or if the canister decides to expose an endpoint that returns its own balance).

Lastly, I must also admit I have not heard compelling arguments why there should be an option at the system level to expose the cycles balance of canisters publicly. The use case of making sure that a canister does not run out of cycles can be covered nicely with e.g. tipjar purely on the application side. I’m also a big fan of the idea that if something can be built on the canister-land it should be built there and not add more options to the system.

9 Likes

The cycles part can be solved if all canisters using a refilling service use a function like __refill which instead of giving cycles balance will specify how many cycles it wants and it can have its own threshold while rounding the number of requested cycles. Since you are all here, you can just agree on something and create the ‘IC refill protocol’.

2 Likes

Thank you for weighing in @dsarlis!

I think most comments here have said they would be in favor of D, so I think when this comes to a proposal that would certainly be the one to be suggested.

I’m not convinced that a sophisticated cycles drain attack is such a huge issue compared with all the other footguns the IC has. Cycles are very cheap compared to other blockchains, there is an instruction limit per call, and there are a limited amount of messages per second that a canister can handle. There is a nice protection mechanism with the freezing_threshold and just depositing a boatload of cycles into anything critical.

Personally when developing a canister I’m far more worried about exposing a single function somewhere that can be used to freely add state and can be used to fill my canister up with junk.

With regards to doing anything that can be done at a canister level there I have to disagree. I heard the same arguments with adding variable intervals to the heartbeat functionality. Needing to rely on another canister is a pain for something so small. And after some feedback from the community it seems like DFINITY wants to provide it at the system level now.

With ethereum and other EVM chains smartcontracts are immutable, can’t run out of cycles, and they don’t suddenly get errors when they reach a certain amount of state.

When you rely on any other service on the IC for something critical you have to make sure that all these things are taken care of which comes down to needing to do a full security audit. And even then you still have to monitor the cycles of every service you are using yourself if you want to be absolutely sure that it won’t break.

I think anytime the system can provide something that is fairly basic that is a far superior option. Option D seem intuitively like far less work to me than the new heartbeat API. It’s just adding an extra permission to an api that already exists.

One thing that would be especially nice compared to the blackhole canister is that everybody calling this system API would have to pay the cycles for it themselves as opposed to the owner of the blackhole canister.

I could just write a script that starts spamming the blackhole e3mmv-5qaaa-aaaah-aadma-cai for free right now. It could make any other canister relying on it way slower and also at the time of writing it only seems to have 4,019,244,002,015 / 1,000,000,000,000 = 4.019244 Trillion cycles.

I’m not convinced that a sophisticated cycles drain attack is such a huge issue compared with all the other footguns the IC has.

I think we should be careful to not add more footguns if we can avoid it.

With regards to doing anything that can be done at a canister level there I have to disagree. I heard the same arguments with adding variable intervals to the heartbeat functionality. Needing to rely on another canister is a pain for something so small. And after some feedback from the community it seems like DFINITY wants to provide it at the system level now.

I believe a big selling point of the IC is the composability and interoperability among dapps. Having the system provide the minimum functionality to enable dapps should be the guiding principle and then people can build solutions on top of the basics (potentially multiple of them that can also cater for different use cases). The heartbeat example is actually a good example of the basic system functionality turning out to be incomplete.

I think anytime the system can provide something that is fairly basic that is a far superior option. Option D seem intuitively like far less work to me than the new heartbeat API. It’s just adding an extra permission to an api that already exists.

I suppose this is the main part where we seem to come from different angles. I do not consider exposing private state of the canisters “fairly basic”. I think it’s something that’s useful in certain use cases (like some you described in this thread) and not necessarily something that any dapp would want. The ability to optionally expose this for certain dapps already exists, so it’s unclear to me what we gain by adding the option to the system as well.

One thing that would be especially nice compared to the blackhole canister is that everybody calling this system API would have to pay the cycles for it themselves as opposed to the owner of the blackhole canister.

That’s not entirely true. If canister_status is public, what would prevent a random user to send such requests? The canister itself would still need to pay for those requests. The management api is not available through query calls so you have to send ingress messages that are charged to the canister whose status you requested – even if you could access it through query calls it would still be charged eventually when queries are charged in general and it would charge the canister. If D is to be implemented, we would also need to be careful about this aspect and not introduce potential attacks. So, to make it more clear, no I don’t think it’s about just adding a flag that allows canister_status to be read by anyone if the flag is true.

3 Likes

I really think footgun is an overstatement. You don’t need super careful analysis of the exact cycles balances to perform a cycle drain attack. When I look at the costs here Computation and Storage Costs | Internet Computer Home the most expensive fixed costs are ingress message reception and update call execution. Both of which you know will be subtracted based on the reply of a call.

Then you have ingress byte reception which you also know as an attacker. If somebody accepts Vecs / Text without a limit just make it a 2MB message. And finally you could measure the time to respond knowing that if it takes long there might be Xnet calls involved or larger computations.

I also don’t think people who don’t open source their code would feel the need to set the flag to public. You can’t trust their canister anyways. For the people who do opensource their code the attacker could just try out which function subtracts the most amount of cycles locally.

I suppose this is the main part where we seem to come from different angles. I do not consider exposing private state of the canisters “fairly basic”

Yeah I do guess we have a difference of opinion here and that’s okay. It’s why I wanted to bring it to a vote, because it is not an issue or feature everybody agrees on.

I think the “everything is public” model of Ethereum and other chains has brought some very powerful network effects with it. The ICP ledger certainly wasn’t designed with privacy in mind, but made other tradeoffs.

(sidenote: cycles already have monetary value through dexes like Sonic. Having private ways to store and send cycles could cause issues with regulators in the future because of AML)

I would be interested in knowing what DFINITY would vote though as they have a pretty big chunk of voting power.

The management api is not available through query calls so you have to send ingress messages that are charged to the canister whose status you requested

I didn’t know this and this would kind of devalue the option for making canister_status public. Why is the canister whose status is being requested being charged instead of the canister that initiated the call?

3 Likes

Why is the canister whose status is being requested being charged instead of the canister that initiated the call?

This is the case when the message is initiated by a end-user, not another canister (in the latter the sending canister is charged). Users can also control canisters and if they send management messages like canister_status we need to charge the target canister just like we do for other ingress messages (ingress messages cannot carry cycles so the “target” canister is the only option to apply the charge).

2 Likes

I just wanted to chime in here and mention that making cycles balance public is not merely a footgun but rather a real security issue. That’s because the attacker can learn how many instructions a specific message has executed by reading the cycles balance of the canister before and after the execution. This kind of side-channel attack is known as Timing attack where the time in our case is represented by executed instructions and it is deterministic, making the attack much simpler.

This attack can be used to steal secret/private data from the canister. It is very difficult to protect against the attack in the presence of such a reliable and deterministic side channel. From the security point of view, I would suggest thinking of the public cycles balance as being equivalent to the public canister state. In other words, option D would be safe only for canisters that are fully public and ideally the developers confirm that with “I am okay if everyone can read the canister state” before enabling the flag.

8 Likes

Im glad someone actually brought up a real argument against public cycles.

Making cycles public does not make cycle draining more of a threat in my opinion and anyone who has tested cycle draining on their own canisters locally would realize that pretty quick.

But timing attack to read state would be an insanely complicated attack to pull off even with cycles exposed and that state they are trying to read is already exposed to node operators anyway so canister owners should probably not treat canister state as bulletproof in the first place

1 Like

I don’t think anybody should put any private data at all on the IC. Any node provider in the subnet could access it. I do think this is a more critical issue than a cycle draining attack.

Note though that the official docs currently suggest the blackhole canister as a solution without this warning. Trust in Canisters | Internet Computer Home


Based on the comments from Dimitri and Ulan I have some newly formed thoughts about what I would like to see.

What I need to know as a canister/dev to trust other canisters for critical services

  • The controllers
  • The module hash
  • The rough cycles balance
  • The freezing threshold

(perhaps when subnets become full and busy the memory and compute allocation will become important too)

All this information is essential if I want to have a dependency on another canister in a trustless manner. I do not think services on the IC can be truly composable and interoperable without it. You’ll always have to handle the case where the canister you rely on stop working.

This is not the case on other EVM chains. People can happily build on e.g the uniswap protocol without worrying about the smart contract disappearing.

Why I suggested to make canister_status (optionally) public

canister_status has all this information and already existed. It seemed like the least effort for the foundation and is an API devs are already familiar with.

Why I don’t like the blackhole canister solution

  • It is a bad developer experience needing to manage / keep in mind an extra canister.
  • If you forget it or somebody drains it on purpose you’ll never know your cycles balance again. This could not happen if it is included in the system API.
  • I think it is bad from a security perspective to need to hand over all the permissions that come with being controller just to expose the canister status. What if somebody hacks the Internet Computer documentation page about the blackhole canister and puts their own malicious canister id instead of bxyoi-2iaaa-aaaag-aanhq-cai? It seems the opposite of the capabilities based system Rossberg was a fan of.
  • it doesn’t prevent the issues mentioned by dsarlis and Ulan here.

New suggestion based on the conversation here

A new API that a canister can choose to expose with a flag

  • Exposes the information in canister_status
  • Where cycles are rounded down to the trillion
  • Which can only be called by principals that are able to pay for it themselves

I know @dsarlis said the foundation is already planning to provide B I wonder if this is something that could potentially be added to it.

1 Like

Amen to what @ulan said. After the whole Spectre disaster, from which our industry still hasn’t fully recovered (and probably never will), it would be extremely unwise to remain knowingly shortsighted about these sort of risks.

@Fulco, AFAIK, a controller canister (black hole or otherwise) is not needed to publish cycle balance. If a canister so wishes, nothing prevents it from itself providing a regular method for inquiring its current balance. I would recommend against that, though.

Keep in mind that the ability to store private data safely in canisters is a stated future goal for the IC. Thus the work on using enclaves etc.

7 Likes

I agree 100% with this. I think there are lots of fully public use cases (NFTs for example), but definitely should be used with this in mind.

Having read the follow up responses here I think I now prefer option B.

I think I’ll refrain from making an NNS proposal. The foundation already seems to be building B and exposing cycles balance besides that seems like a more nuanced issue that I don’t think is ready for an NNS vote.

This issue seems more suited for something like a standards committee where different bigger players signal there support. Hopefully ICRC can be used for to decide on a common way to expose cycle balances that don’t give away too much information (e.g by rounding it down to a certain level).

3 Likes

I wanted to drop this not very popular AgentJs feature here.
It currently provides “module_hash”, but doesn’t give “controllers”
I think the documentation says it should The Internet Computer Interface Specification | Internet Computer Home

import { CanisterStatus, HttpAgent } from "@dfinity/agent";

const requestCanisterStatus = (canister_id) => {
  const agent = new HttpAgent({ host: `https://ic0.app` });
  //if (local) await agent.fetchRootKey();
  return CanisterStatus.request({
    canister_id,
    agent,
    paths: ["controllers", "module_hash"],
  });
};
1 Like

@infu I’m not sure what you meant here. Are you saying that there’s some bug in AgentJS? If so, it should be raised as a separate topic (or directly as in issue in the repository). Indeed, you should be able to request both module_hash and controllers from the system state tree.

1 Like

Relevant post that I created sometime back:

This would also make it possible for the canister itself to make that information public with its own public API if it so chooses.

1 Like

Thanks for pointing this out. I’ll reach out to the owners to update the docs. I guess they were not aware of the side-channel attack.

FWIW, a variant of option D that is limited to fully public canisters seems very useful to me. For example, if the flag that the developer would set is named something like “public_canister” or “canister_with_public_state”, then exposing the cycles balance would useful and safe. In that case options B and D are not exclusive to each other and we could support both.

4 Likes