1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Fix: eth_sign does not validate input (#12679)

* Fix #5039

* Converted function into async

* Added more explicit explanation of why the number of bits for EcSign

* eth_sign and eth_personalSign now report errors correctly back to the user

* Added leeway to unsigned message byte check

* Fix lint
This commit is contained in:
Olaf Tomalka 2021-11-19 17:05:24 +01:00 committed by GitHub
parent fb6375472e
commit 3f3479bf6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 43 deletions

View File

@ -79,9 +79,9 @@ export default class MessageManager extends EventEmitter {
* @returns {promise} after signature has been * @returns {promise} after signature has been
* *
*/ */
addUnapprovedMessageAsync(msgParams, req) { async addUnapprovedMessageAsync(msgParams, req) {
return new Promise((resolve, reject) => { const msgId = this.addUnapprovedMessage(msgParams, req);
const msgId = this.addUnapprovedMessage(msgParams, req); return await new Promise((resolve, reject) => {
// await finished // await finished
this.once(`${msgId}:finished`, (data) => { this.once(`${msgId}:finished`, (data) => {
switch (data.status) { switch (data.status) {
@ -93,6 +93,10 @@ export default class MessageManager extends EventEmitter {
'MetaMask Message Signature: User denied message signature.', 'MetaMask Message Signature: User denied message signature.',
), ),
); );
case 'errored':
return reject(
new Error(`MetaMask Message Signature: ${data.error}`),
);
default: default:
return reject( return reject(
new Error( new Error(
@ -233,6 +237,19 @@ export default class MessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'rejected'); this._setMsgStatus(msgId, 'rejected');
} }
/**
* Sets a Message status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId - The id of the Message to error
*
*/
errorMessage(msgId, error) {
const msg = this.getMsg(msgId);
msg.error = error;
this._updateMsg(msg);
this._setMsgStatus(msgId, 'errored');
}
/** /**
* Clears all unapproved messages from memory. * Clears all unapproved messages from memory.
*/ */
@ -304,7 +321,7 @@ export default class MessageManager extends EventEmitter {
* @returns {string} A hex string conversion of the buffer data * @returns {string} A hex string conversion of the buffer data
* *
*/ */
function normalizeMsgData(data) { export function normalizeMsgData(data) {
if (data.slice(0, 2) === '0x') { if (data.slice(0, 2) === '0x') {
// data is already hex // data is already hex
return data; return data;

View File

@ -107,6 +107,9 @@ export default class PersonalMessageManager extends EventEmitter {
), ),
); );
return; return;
case 'errored':
reject(new Error(`MetaMask Message Signature: ${data.error}`));
return;
default: default:
reject( reject(
new Error( new Error(
@ -254,6 +257,19 @@ export default class PersonalMessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'rejected'); this._setMsgStatus(msgId, 'rejected');
} }
/**
* Sets a Message status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId - The id of the Message to error
*
*/
errorMessage(msgId, error) {
const msg = this.getMsg(msgId);
msg.error = error;
this._updateMsg(msg);
this._setMsgStatus(msgId, 'errored');
}
/** /**
* Clears all unapproved messages from memory. * Clears all unapproved messages from memory.
*/ */

View File

@ -17,6 +17,7 @@ import LedgerBridgeKeyring from '@metamask/eth-ledger-bridge-keyring';
import LatticeKeyring from 'eth-lattice-keyring'; import LatticeKeyring from 'eth-lattice-keyring';
import EthQuery from 'eth-query'; import EthQuery from 'eth-query';
import nanoid from 'nanoid'; import nanoid from 'nanoid';
import { ethErrors } from 'eth-rpc-errors';
import { captureException } from '@sentry/browser'; import { captureException } from '@sentry/browser';
import { import {
AddressBookController, AddressBookController,
@ -61,7 +62,7 @@ import AlertController from './controllers/alert';
import OnboardingController from './controllers/onboarding'; import OnboardingController from './controllers/onboarding';
import ThreeBoxController from './controllers/threebox'; import ThreeBoxController from './controllers/threebox';
import IncomingTransactionsController from './controllers/incoming-transactions'; import IncomingTransactionsController from './controllers/incoming-transactions';
import MessageManager from './lib/message-manager'; import MessageManager, { normalizeMsgData } from './lib/message-manager';
import DecryptMessageManager from './lib/decrypt-message-manager'; import DecryptMessageManager from './lib/decrypt-message-manager';
import EncryptionPublicKeyManager from './lib/encryption-public-key-manager'; import EncryptionPublicKeyManager from './lib/encryption-public-key-manager';
import PersonalMessageManager from './lib/personal-message-manager'; import PersonalMessageManager from './lib/personal-message-manager';
@ -1843,14 +1844,22 @@ export default class MetamaskController extends EventEmitter {
* @param {Object} msgParams - The params passed to eth_sign. * @param {Object} msgParams - The params passed to eth_sign.
* @param {Function} cb - The callback function called with the signature. * @param {Function} cb - The callback function called with the signature.
*/ */
newUnsignedMessage(msgParams, req) { async newUnsignedMessage(msgParams, req) {
const promise = this.messageManager.addUnapprovedMessageAsync( const data = normalizeMsgData(msgParams.data);
msgParams, let promise;
req, // 64 hex + "0x" at the beginning
); // This is needed because Ethereum's EcSign works only on 32 byte numbers
this.sendUpdate(); // For 67 length see: https://github.com/MetaMask/metamask-extension/pull/12679/files#r749479607
this.opts.showUserConfirmation(); if (data.length === 66 || data.length === 67) {
return promise; promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req);
this.sendUpdate();
this.opts.showUserConfirmation();
} else {
throw ethErrors.rpc.invalidParams(
'eth_sign requires 32 byte message hash',
);
}
return await promise;
} }
/** /**
@ -1859,24 +1868,23 @@ export default class MetamaskController extends EventEmitter {
* @param {Object} msgParams - The params passed to eth_call. * @param {Object} msgParams - The params passed to eth_call.
* @returns {Promise<Object>} Full state update. * @returns {Promise<Object>} Full state update.
*/ */
signMessage(msgParams) { async signMessage(msgParams) {
log.info('MetaMaskController - signMessage'); log.info('MetaMaskController - signMessage');
const msgId = msgParams.metamaskId; const msgId = msgParams.metamaskId;
try {
// sets the status op the message to 'approved' // sets the status op the message to 'approved'
// and removes the metamaskId for signing // and removes the metamaskId for signing
return this.messageManager const cleanMsgParams = await this.messageManager.approveMessage(
.approveMessage(msgParams) msgParams,
.then((cleanMsgParams) => { );
// signs the message const rawSig = await this.keyringController.signMessage(cleanMsgParams);
return this.keyringController.signMessage(cleanMsgParams); this.messageManager.setMsgStatusSigned(msgId, rawSig);
}) return this.getState();
.then((rawSig) => { } catch (error) {
// tells the listener that the message has been signed log.info('MetaMaskController - eth_sign failed', error);
// and can be returned to the dapp this.messageManager.errorMessage(msgId, error);
this.messageManager.setMsgStatusSigned(msgId, rawSig); throw error;
return this.getState(); }
});
} }
/** /**
@ -1923,23 +1931,27 @@ export default class MetamaskController extends EventEmitter {
* @param {Object} msgParams - The params of the message to sign & return to the Dapp. * @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @returns {Promise<Object>} A full state update. * @returns {Promise<Object>} A full state update.
*/ */
signPersonalMessage(msgParams) { async signPersonalMessage(msgParams) {
log.info('MetaMaskController - signPersonalMessage'); log.info('MetaMaskController - signPersonalMessage');
const msgId = msgParams.metamaskId; const msgId = msgParams.metamaskId;
// sets the status op the message to 'approved' // sets the status op the message to 'approved'
// and removes the metamaskId for signing // and removes the metamaskId for signing
return this.personalMessageManager try {
.approveMessage(msgParams) const cleanMsgParams = await this.personalMessageManager.approveMessage(
.then((cleanMsgParams) => { msgParams,
// signs the message );
return this.keyringController.signPersonalMessage(cleanMsgParams); const rawSig = await this.keyringController.signPersonalMessage(
}) cleanMsgParams,
.then((rawSig) => { );
// tells the listener that the message has been signed // tells the listener that the message has been signed
// and can be returned to the dapp // and can be returned to the dapp
this.personalMessageManager.setMsgStatusSigned(msgId, rawSig); this.personalMessageManager.setMsgStatusSigned(msgId, rawSig);
return this.getState(); return this.getState();
}); } catch (error) {
log.info('MetaMaskController - eth_personalSign failed', error);
this.personalMessageManager.errorMessage(msgId, error);
throw error;
}
} }
/** /**

View File

@ -835,7 +835,8 @@ describe('MetaMaskController', function () {
let msgParams, metamaskMsgs, messages, msgId; let msgParams, metamaskMsgs, messages, msgId;
const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813'; const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813';
const data = '0x43727970746f6b697474696573'; const data =
'0x0000000000000000000000000000000000000043727970746f6b697474696573';
beforeEach(async function () { beforeEach(async function () {
sandbox.stub(metamaskController, 'getBalance'); sandbox.stub(metamaskController, 'getBalance');
@ -885,6 +886,19 @@ describe('MetaMaskController', function () {
assert.equal(messages[0].status, TRANSACTION_STATUSES.REJECTED); assert.equal(messages[0].status, TRANSACTION_STATUSES.REJECTED);
}); });
it('checks message length', async function () {
msgParams = {
from: address,
data: '0xDEADBEEF',
};
try {
await metamaskController.newUnsignedMessage(msgParams);
} catch (error) {
assert.equal(error.message, 'eth_sign requires 32 byte message hash');
}
});
it('errors when signing a message', async function () { it('errors when signing a message', async function () {
try { try {
await metamaskController.signMessage(messages[0].msgParams); await metamaskController.signMessage(messages[0].msgParams);