NNS Proposal: Make canister_status public to anyone

It might be worthwhile to list all implications of the flag in the proposal.
E.g. if the canister sets the flag, then anyone would be able to read:

  • the canister status,
  • the Wasm source binary,
  • the Wasm and stable memory pages.
  • any other user-visible settings/fields of the canister.

Even if reading some of these is not technically possible today, it may become possible in the future and public canister should not break then.

Do you mean that it is the plan in the future for developers to be able to download.

  • the Wasm source binary,
  • the Wasm and stable memory pages.

And that in the future this flag would expose that publicly as well. Or do you mean that somebody could more easily find an exploit to retrieve that extra information with the flag enabled?

Do you mean that it is the plan in the future for developers to be able to download …

Yes, a public canister should have no expectation of privacy. On the IC side we should be free to provide tools that allow anyone to download the code and the state of a public canister.

That’s a good feature to have. However, probably need more flags. It’s not worth becoming that public just to help scripts refill canisters

1 Like

That’s my point about the side-channel attack: making cycles balance public is only safe for fully public canisters. If a canister have some secret data, then it must not make its cycles balance public (otherwise it may accidentally leak some bits of the secret data).

3 Likes

Have you considered placing the _refill (reporting how many cycles are needed) function inside CDK’s, just like _tmp_if_hack is right now getting added inside all Motoko canisters?

I might just support this as the defacto state. Until we have enclaves we are all just pretending that any of this is private. A node provider could dump this for any canister at any time.

If this was all public, downloadable, and if the replica would return a certifiable hash of the memory, then we could offload a good bit of read traffic to read only user run replicas.

4 Likes

I hope the enclaves will come in the future and I consider that as an implementation issue.

At the protocol spec level I think it is worthwhile to keep the distinction between private and public canisters.

2 Likes

Hi Fulco, I’d definitely support this as something that can be enabled by a flag governed by the controller. The ability to publish fully public canisters fits well within the IC ethos.

2 Likes

The only issue here would be that in between each call to canister_status, the canister is also supposedly open to and accepting calls from additional users, which makes it significantly harder for an attacker to attribute the next cycles balance completely to the call they just made. Imagine an attacker trying to thread the needle for this type of an attack on a larger application like DSCVR.

That being said, for inactive canisters, this would be easier. You could always up the variance on the mask to +/- 10,000,000 cycles, but a determined attacker could still potentially figure this out after enough time given the cycles balance distribution between calls (as long as the canister is inactive).

In response to @ulan’s point here:

If a canister’s source code is completely open sourced, one additional issue I see would be if a secret or access token to private data is inserted (dynamically) into the canister and stored. Then if some equivalence check is made, this secret or any sensitive data in the canister (not in the source code) could be deduced through a Scepter-style cycles attack.

In this scenario, open sourcing would make it easier for the attacker to simulate various scenarios and get to the point where they know the cycles cost of all different pieces of the API, and then be able probe for the specific data they wish to extract.

2 Likes

Been following this thread in it’s entirety and the direction seems really promising. Much appreciation for all the thoughtful contributions thus far. There are two notes that would help bring this proposal home, for me anyways. First, on timing attacks:

Please excuse this stab in the dark that I’m about to take–I am not up to speed on timing attacks–but I want to poke at the notion that publishing cycles balance is tantamount to publishing all internal state. I’ll start with a question, which essentially question boils down to this: how much surface area does an attacker need to compromise secrets via a timing attack? Let me expand.

A canister can of course inspect message callers, restricting access such that only certain principal(s) stored in internal state can call any of its methods. (There’s also message inspection, but this doesn’t cover inter-canister calls.) It seems to me that a canister sufficiently “locked down” in this manner might provide the exact same timing data in response to all of an attacker’s messages, since methods would uniformly route them into the same “permission denied” trap. I’d test this notion, but perhaps those of us more familiar timing attacks could disprove this for me theoretically and save me some experimentation. :grin:

Regardless of how right/wrong that idea is, I think it’s easy to conceptualize a project that might want reveal cycles balance, without throwing open the barn doors of internal state, simply because it’s possible to pick the lock. The suggestion here to lump in public stable memory pages seems like a possible overstep:

I would be in favor of an implementation of the “public canister status flag” that makes risks clear, makes good things easy and bad things hard, but doesn’t necessarily decide really broad swathes of your project’s security/privacy strategy for you.

This flag seems to be obviously useful and to have broad support in this thread. Perhaps I’ve missed the mark, but coupling it with “make my stable memory pages public” would turn many people off, and perhaps is not necessary. (Sorry if I’m asking you to make the same points again and again @ulan and @rossberg :sweat_smile:)

While I do think the canisterland implementation of cycles balances seems quite workable for many purposes such as topups, community monitoring, etc., it might too heavy, intrusive, etc. for some projects to take the blackhole approach. Public cycles balances behind a flag could be a more attractive option in those instances, but making your canister’s entire internal state explicitly public might kill that notion altogether.

1 Like

Hi @jorgenbuilder. Thanks for the thoughtful comment. You’re right that in some cases the canister developer can protect against the timing attacks by allow-listing the caller principals (note that it is not the perfect protection because it doesn’t protect against one principal learning private data of another principal).

I was mostly worried about the developers that write code like this:

#[update]
async fn foo(input: String) {
   if input != secret {
      return;
   }
   // Do more work.
}

The attacker can easily learn secret by calling the method with different input strings character by character and checking the cycle balance of the canister after each call.

It is difficult to catch timing attack issues because this kind of code can be deep inside a program. The developer would need to ensure that no private data processing depends on the user input. Even if the developer does the security audit and ensures this property for the canister now, some small change in the future can introduce a timing attack vulnerability.

In summary, if we provide a flag that makes cycles balance public, then we would have to assume that some private data of some canisters may leak. To avoid misleading the developers, we would need to name the flag appropriately, e.g. PUBLIC_CYCLES_BALANCE_MAY_LEAK_DATA. The unfortunate part here is that we don’t know how to avoid leaking data and cannot give guidelines on using this flag safely. So we would be giving the developers a footgun.

If the community feels that the benefits of the flag overweigh the risks, we could try putting that up for vote.

4 Likes

It’s been a while since someone posted on this topic, and it’s still an open issue.
IMHO by exposing an approximate value of cycle balance (e.g. rounded up/down or with jitter added), most if not all of the security concerns should be resolved.
There are also other smart things we can do to reduce risk. But IMHO it would be enough to say in the spec that an approximate value of cycle balance is exposed publicly, and then give no further guarantees or limitations.
So, with this, would be ready to submit a motion proposal?

@sat There is no need for it. Those who want the canister_settings api exposed can do so with a custom method or set the black-hole canister or similar as a controller.

At the CYCLES-TRANSFER-STATION, users own their own cycles-bank which can hold and transfer the native cycles. It is better if the cycles-balance is kept private.

Canisters can call the new canister_info management canister api to get on-chain the wasm module hash and canister controllers of every canister.

Hey @kpeacock, just wanted to shine a light again on this agent-js bug that’s still hanging around. When I use this snippet

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

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

It’s not giving me all the controllers. The blackhole controller is missing from the list, and there’s this weird thing happening: when I remove the blackhole controller, my dfx identity also suddenly disappears from the controller list.

I see ways to work around it but this could be a security issue.

It seems that when I go to icscan.io the same false controller list shows up so somebody could pretend that a canister doesn’t have any controllers by showing icscan.io as false proof.

Thanks for calling this out! I have some work in progress on another canister_status topic and this is a good time for me to look into it.

@Fulco do you have a mainnet canister ID I can use while troubleshooting?

Yes will send you a dm

Pinging this thread as I’m also interested in having this publicly known (or at the very least opt-in). I wish for an on-chain verified leaderboard of the Open Internet dApps. :pray:

2 Likes

@here This topic has now been open for a while and comes up again and again. For this reason, I’d like to start a small poll to gauge the current sentiment

  • Allow canister_status to be made public with the understanding this could expose secrets and could be extended to make all code & state of the canister public, i.e, public canister status == public canister.
  • Allow canister_status to be made public with a fuzzy or low-resolution cycles balance.
  • Standardize a canister/app level status/metrics endpoint that includes this information.
  • Don’t do anything. Black hole canister approach is good enough.
0 voters
5 Likes

I voted for a public canister option. I ran across a product called seatbelt that for Evm governance is able to simulate what a governance proposal is going to do because it has access to state. Eventually this would be great to have, especially for dao canisters so that we can track what other messages a governance proposal is going to trigger. This will require being able to pull state and wasm. This would be great for public infrastructure.

1 Like