Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove tx.origin #683

Closed
PeterBorah opened this issue Jun 24, 2016 · 43 comments
Closed

Remove tx.origin #683

PeterBorah opened this issue Jun 24, 2016 · 43 comments

Comments

@PeterBorah
Copy link

PeterBorah commented Jun 24, 2016

Summary

tx.origin is a security vulnerability, breaks compatibility with other contracts including security contracts, and is almost never useful. Removing it would make Solidity more user-friendly. If there are exceptional cases where access to the transaction origin is needed, a library using in-line assembly can provide it.

The Problems

  1. tx.origin is a security vulnerability. As we recently saw with the Mist wallet, using tx.origin makes you vulnerable to attacks comparable to phishing or cross-site scripting. Once a user has interacted with a malicious contract, that contract can then impersonate the user to any contract relying on tx.origin.

  2. tx.origin breaks compatibility. Using tx.origin means that your contract cannot be used by another contract, because a contract can never be the tx.origin. This breaks the general composability of Ethereum contracts, and makes them less useful. In addition, this is another security vulnerability, because it makes security-based contracts like multisig wallets incompatible with your contract.

  3. tx.origin is almost never useful. This is the most subjective point, but I have yet to come across a use of tx.origin that seemed legitimate to me. I welcome counter-examples, but I've written dozens or hundreds of smart contracts without needing it, and I have never heard of anyone else needing it either.

Rationale for Removal

Solidity's design philosophy is to prioritize security and reliability over expressiveness. In other cases where behavior is unreliable, Solidity does not expose it. (For instance, there is no way to call an external contract and retrieve the return value if the signature is not known ahead of time.)

The "escape clause" is in-line assembly, which allows the creation of libraries to do anything expressible as EVM assembly. Behavior that is unsafe and unreliable is best kept in libraries, rather than given to all users as part of the core language.

@aakilfernandes
Copy link

SafeMarket currently has a vulnerability due to use of tx.origin. Thankfully, Peter explained this bug to me otherwise I would have fallen into the trap.

@chriseth
Copy link
Contributor

I'm not sure removing is a good idea, but we can add a warning.

@PeterBorah
Copy link
Author

Why not?

@chriseth
Copy link
Contributor

chriseth commented Jun 24, 2016

Because it will break backwards-compatibility. If you are saying that a warning is not enough and will not be noticed, we should revisit how people compile their contracts.

@chriseth
Copy link
Contributor

If someone wants to implement that, please take a look at #677 which is very similar. This one has to go into visit(MemberAccess ...).

@PeterBorah
Copy link
Author

Fair enough. Is Solidity committed to backwards-compatibility in general?

I would recommend at least adding it to the list of things to change when you do the next breaking change. Any code that would be broken by this needs to be broken, because it is almost certainly insecure in some way.

@redsquirrel
Copy link
Contributor

Any code that would be broken by this needs to be broken, because it is almost certainly insecure in some way.

Good point here 👆

@VoR0220
Copy link
Member

VoR0220 commented Jun 24, 2016

I think @PeterBorah makes a good point here. I think backwards compatibility for this security flaw might be worth breaking just this time.

@pipermerriam
Copy link
Member

So far the only times I've used tx.origin are:

  1. Bypassing stack depth checking
  2. Source of entropy.

@slothbag
Copy link

I have contracts that ripple calls up a chain of contracts.. and I need to know which account was the originator. I use tx.origin for that.

@PeterBorah
Copy link
Author

PeterBorah commented Jun 25, 2016

@slothbag Regardless of the outcome of this pull request, double and triple check that you're actually secure there. Chances are, there are ways for malicious contracts to do unexpected things by impersonating your users or your contracts. Is it ok if an attacker is able to call any of your functions at any time, with any arguments, while appearing to be a legitimate user? If not, tx.origin isn't safe.

Also consider whether it's ok that your users can't use multisig wallets, and can't be DAOs or anything like that.

If you're interested, I'd be happy to take a look at your contracts and offer advice for structuring your contracts so you don't need tx.origin. I am on a quest to eliminate it from existence!

@slothbag
Copy link

Thanks @PeterBorah I will definitely revisit my usage of it.

@Nashatyrev
Copy link
Member

One of our contracts allows registering only non-contract address. We are checking this with msg.sender == tx.origin

@PeterBorah
Copy link
Author

@Nashatyrev: Why do you want to do this? It vastly reduces your user's security options, because they can't use multisig, key revocation, or other contract-based security systems. As a user, I would not appreciate being forced to use a dangerous method (a bare private/public keypair) for interacting with a contract.

Additionally, this won't work the way you expect it to after EIP 101.

@Nashatyrev
Copy link
Member

Nashatyrev commented Jul 20, 2016

E.g. you have a Token contract (address -> balance), and you want to get fees from transferring tokens between owners. If you allow a contract to own a token account then those token can be locked and transferred between owners without any fees (i.e. kind of derivative token contract). Non-contract only accounts prevent this. As a user you may grant any contract with access to your account (which still prevents token locking) for the benefits you've mentioned.

Not sure why EIP 101 should break this? The transaction will still have a signer and the contract invocation will still have a caller.

@PeterBorah
Copy link
Author

PeterBorah commented Jul 21, 2016

As a user you may grant any contract with access to your account (which still prevents token locking) for the benefits you've mentioned.

This isn't true. There will still be a private key somewhere with access to the tokens, which means there's no way to use any other form of security exclusively. If someone gets access to the private key, it's game over.

(It's also just annoying that I have to generate a private key just for your one dapp, rather than using the security system I use for literally everything else.)

Not sure why EIP 101 should break this? The transaction will still have a signer and the contract invocation will still have a caller.

I don't know exactly what the semantics will be, but either tx.origin will be the origin address, in which case it is useless for telling who sent it, or it will be the address of the contract that pays the gas, in which case it could easily be the sort of contract you're hoping to prevent.

@Nashatyrev
Copy link
Member

You may generate a key, create an account, grant account access to any contract you like and immediately destroy the key.
The trick is you can't prove others you have destroyed the key and thus the tokens can't be assumed locked. So any token transfer should be still performed via the Token contract and transfer fees get paid.
Can you please tell what security system are you referring?

EIP 101: Do you mean this change is going to be VM backward incompatible?

@PeterBorah
Copy link
Author

You may generate a key, create an account, grant account access to any contract you like and immediately destroy the key.

Yeah, that works I guess, though you have to hope that you didn't leak the key during the creation and signing phase. Not as secure as I'd like, but not terrible.

Can you please tell what security system are you referring?

I don't have one set up yet, so I was referring to the hypothetical future. But any security system based on smart contracts, which will be pretty much any of them. For instance, anything with a multisig component, or anything where you can change keys or where you have "emergency override" keys. We're building this sort of system at Ownage for our users, and there will be lots more coming out in the near-to-medium-term future. The Mist multisig wallets are a simple example of this pattern.

EIP 101: Do you mean this change is going to be VM backward incompatible?

Well, it's definitely backwards incompatible (until now all transactions had to be signed in a specific format, after EIP 101 any transaction is potentially valid regardless of signature). But in this specific case, it's not the backwards incompatibility that's the problem, but the general functionality implied by the EIP. Since you can have your "account" work based on any code you write, the "account" can become the sort of derivative you don't want.

@axic
Copy link
Member

axic commented Jul 25, 2016

@PeterBorah:

I would recommend at least adding it to the list of things to change when you do the next breaking change.

It would definitely make sense starting a list of possible breaking changes and have them in the next version. Since we're following semver, 0.4.x could have breaking changes.

I would include not only security related changes, but remove any possible dead weight which could be a burden in the future evolution of the language.

@chriseth would it make sense having a separate issue opened for it or a git issue tag is sufficient? Judging by a quick browse there are plenty of proposed breaking changes.

@chriseth
Copy link
Contributor

Yes, such a list makes sense. I actually prefix all PRs that introduce breaking change with BREAKING.

As far as tx.origin is concerned, I am still not convinced that banning it to inline assembly is a good thing. If you do access control, then every single example uses msg.sender and not tx.origin. We also don't forbid users to do access control using passwords in contracts, but we tell users in the documentation not to do it.

I'm fine with adding a static analysis check in browser-solidity to search for tx.origin, but I would not want to remove it from the language.

@PeterBorah
Copy link
Author

We also don't forbid users to do access control using passwords in contracts, but we tell users in the documentation not to do it.

There are lots of things that Solidity doesn't allow you to do. It's a statically typed language! This is the same sort of thing: tx.origin is a bug that is can be detected at compile time.

I'm sympathetic to the argument that power is more important than safety, but that doesn't seem to be Solidity's philosophy in any other situation.

@chriseth
Copy link
Contributor

@akrolak
Copy link

akrolak commented May 20, 2017

Actually I have opposite problem, I want to use tx.origin for personal account but it returns contract address in spoke and hub model :(

@ytrezq
Copy link

ytrezq commented Jun 17, 2018

@akrolak you’re getting tx.orgin to return a contract address ? I’m really interested. Please explain how you got that problem ! (including through e mail).

@bitcoinwarrior1
Copy link

@PeterBorah wouldn't one need tx.origin if they are delegating on behalf of the caller? at the moment it is not possible to keep the msg.sender for the original caller when using a proxy contract for example.

@jooray
Copy link
Contributor

jooray commented Feb 3, 2019

@PeterBorah wouldn't one need tx.origin if they are delegating on behalf of the caller? at the moment it is not possible to keep the msg.sender for the original caller when using a proxy contract for example.

No, you would not need that, even worse, it is really a bad idea.

You should keep msg.sender by using a trusted proxy contract and then you can verify that msg.sender is approved proxy contract and then you have original sender as an argument to transaction.

On the other hand, if you use tx.origin, you allow any contract deployed on the blockchain to be a proxy, meaning someone wants to buy a CryptoDog or play with some stupid game and that contract could act on behalf of the user, if you rely on tx.origin. Please, don't do that.

+1 for removing tx.origin

@HarryR
Copy link

HarryR commented Feb 3, 2019

On the other hand, if you use tx.origin, you allow any contract deployed on the blockchain to be a proxy, meaning someone wants to buy a CryptoDog or play with some stupid game and that contract could act on behalf of the user, if you rely on tx.origin. Please, don't do that.

This is a valid use case, as I said before - I don't think it's justified to remove useful functionality solely to prevent people from shooting themselves in the foot.

In reposte, I propose to remove malloc from C, because it often leads to memory leaks.

@Lucienest
Copy link

I oppose the idea of removing tx.origin, Many users do utilize it for different checks. msg.sender couldn't be an alternative to tx.origin as both serve a different purpose.

mfornet added a commit to Near-One/rainbow-bridge that referenced this issue Mar 11, 2021
Instead use env::predecessor_account_id. Using signer_account_id
has the same problems of using tx.origin in Ethereum. See link
below for more details:

ethereum/solidity#683
mfornet added a commit to Near-One/rainbow-bridge that referenced this issue Mar 14, 2021
* fix: Don't use env::signer_account_id

Instead use env::predecessor_account_id. Using signer_account_id
has the same problems of using tx.origin in Ethereum. See link
below for more details:

ethereum/solidity#683

* fix: Use fixed near-sdk-version

Need to use fixed near-sdk-version for compatibility
with rainbow-token-connector. This restriction will
be lifted once the special commit used by the connector
lands on near-sdk-rs.

* Add test to check near contracts are compiled correctly

* Fix pipeline.yml syntax

* Update near-sdk from eth-types

* Recompile contracts

* fix: Use new version of the connector.

TODO: Don't use current pinned commit from token-connector,
instead wait until it is merged into master

* Update arguments to deploy locker

using new interface

* Use fixed amount of gas

* Compile contracts

* Update tests

* Update cli/package.json

Use commit on master.
@dzimbeck
Copy link

dzimbeck commented Jul 25, 2021

Using tx.origin == msg.sender is extremely useful. For example lets say I want a function that can only be called by a private key and not a contract. For example, I'm writing a BitBay bridge for my coin with a dynamic matrix-like supply and want to list on Uniswap so I have a whitelist system for typical erc20 calls like transferfrom and want to detect deposits to the AMM. Thus I register it in my contract by detecting LP token shift on Uniswap and last user to interact with AMM and issue special LP coins that match the matrix(either that or convince every AMM we wanna list to change which is impossible). Therefore I want to deny new AMM that aren't registered because their misunderstanding of the dynamic supply can cause losses(supply shift causes array elements to be inappropriately understood by LP pool ratio and only array of LP ratios by our contract can fix it). Even more frustrating that anyone can open an AMM pair on another exchange not knowing risk. The typical ERC20 functions are therefore intentionally only allowed to be used by a user (tx.origin == msg.sender) OR a whitelisted contract. Contract can automatically be notified on new attempt to interact. Whitelisting can of course be voted in democratically (the users agree its not an AMM). This does not limit other contracts by the way. Thus the coin has its own methods for sending not breaking any potential custom contracts that understand the special matrix-like balances!

As far as I know (please someone correct me if I'm mistaken) there is only one way to know if a user is not a contract and thats tx.origin==msg.sender. Removing tx.origin will break anything that needs to know that info and those contracts may not be able to easily figure out if user isn't a contract.

Granted we can just let LPs know to tread with caution when attempting to deposit to other protocols. Or "let the buyer beware" that they didn't read the code. Well I would much rather limit this specific coins erc20 functionality and add a second way to access the coins functions(or ask to be whitelisted). Adding a way to bypass the check means users can of course still use all functionality with contracts but at least existing contracts can still be used not preventing listings on AMMs. Removing tx.origin will break this functionality. I don't see how removing it to "protect users from themselves" makes sense when you only need to issue a warning. Can't the warning detect that tx.origin is being used to see if its msg.sender or not?! Is there an alternative way to get tx.origin via assembly so if its removed my contract doesn't break? Or is there an alternative way to guarantee msg.sender isn't a contract?

EDIT:
I eventually found a better solution to just detect AMM by looking for it's functions and having another contract handle it's accounting. Then made a custom router for deposits and withdraws and some detection that can revert on balance checks in some situations. But still, it's useful to know that the origin is not a contract(nor one that's created later) if you require a specific chain of calls.

@SKFrozenCloud
Copy link

SKFrozenCloud commented Oct 24, 2021

On the other hand, if you use tx.origin, you allow any contract deployed on the blockchain to be a proxy, meaning someone wants to buy a CryptoDog or play with some stupid game and that contract could act on behalf of the user, if you rely on tx.origin. Please, don't do that.

This is a valid use case, as I said before - I don't think it's justified to remove useful functionality solely to prevent people from shooting themselves in the foot.

In reposte, I propose to remove malloc from C, because it often leads to memory leaks.

I concur, malloc is really dangerous.

As a Solidity developer, I think it is expected of you to know the pitfalls and the limitations of any function or code you write.
In some instances there are no bad consequences of a "bad" function (such as tx.origin) being used.
Removing it or changing its functionality is not the way to go.

In my contract I assume that "tx.origin == msg.sender" could be completely manipulated but it does not matter in my particular instance.
It is not used for security purposes.

@axic axic removed their assignment Nov 8, 2021
karim-en pushed a commit to Near-One/rainbow-bridge that referenced this issue Dec 20, 2021
* fix: Don't use env::signer_account_id

Instead use env::predecessor_account_id. Using signer_account_id
has the same problems of using tx.origin in Ethereum. See link
below for more details:

ethereum/solidity#683

* fix: Use fixed near-sdk-version

Need to use fixed near-sdk-version for compatibility
with rainbow-token-connector. This restriction will
be lifted once the special commit used by the connector
lands on near-sdk-rs.

* Add test to check near contracts are compiled correctly

* Fix pipeline.yml syntax

* Update near-sdk from eth-types

* Recompile contracts

* fix: Use new version of the connector.

TODO: Don't use current pinned commit from token-connector,
instead wait until it is merged into master

* Update arguments to deploy locker

using new interface

* Use fixed amount of gas

* Compile contracts

* Update tests

* Update cli/package.json

Use commit on master.
@CodeMustRule
Copy link

I have used it as require( msg.sender == owner && tx.origin != msg.sender ) leaving it to you to reason its usage.

I have seen it used too as require( tx.origin == msg.sender ) for efficiency purposes and in the corresponding infinite though processes of each programmer.

Breaking contracts in a blockchain where code is immutable is just a mistake, is a NEVER do that. This is even more critical when this discussion is about personal taste. Making not expert users see their funds lock, because his transaction that previously worked and now after your quest is successful it won't ever succeed, is morally wrong.

The logic behind that you believe that other programmers make mistakes in their code, makes delegatecall, call and practically every feature that is corruptible be subject to the same treatment.

@lukehutch
Copy link
Contributor

I'm trying to come up with a way to produce a unique transaction ID for each transaction, in order to bind the lifecycle of some piece of state to a single transaction -- and the solution may require the use of tx.origin (so I don't think it should be removed).

@svylabs
Copy link

svylabs commented Dec 22, 2022

I want to check if the user sending a transaction is a token holder before allowing certain operations in the contract. The flow goes like this

User -> ContractA -> MainContract (or)
User -> ContractB -> MainContract

In this case, the MainContract checks whether the user has the token. The only way it's currently possible is through the use of tx.origin. It's a legitimate use IMO. If I use msg.sender or have the ContractA or ContractB pass in the value which the MainContract checks, then someone can create a ContractC, which holds some coins and allows any user to use the MainContract.

Imagine a case where the MainContract should only be accessible by pass holders(eg: NFT or ERC20 token). If I use msg.sender in MainContract instead of tx.origin, a ContractC can hold that pass and all users can use ContractC to call the MainContract, which defeats the purpose of the pass token.

So I don't think tx.origin should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests