diff --git a/.changeset/warm-geese-dance.md b/.changeset/warm-geese-dance.md new file mode 100644 index 000000000..5deb7a3e6 --- /dev/null +++ b/.changeset/warm-geese-dance.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`Base64`: Fix issue where dirty memory located just after the input buffer is affecting the result. diff --git a/contracts/mocks/Base64DirtyUpgradeable.sol b/contracts/mocks/Base64DirtyUpgradeable.sol new file mode 100644 index 000000000..1b0c41909 --- /dev/null +++ b/contracts/mocks/Base64DirtyUpgradeable.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; +import {Initializable} from "../proxy/utils/Initializable.sol"; + +contract Base64DirtyUpgradeable is Initializable { + struct A { + uint256 value; + } + + function __Base64Dirty_init() internal onlyInitializing { + } + + function __Base64Dirty_init_unchained() internal onlyInitializing { + } + function encode(bytes memory input) public pure returns (string memory) { + A memory unused = A({value: type(uint256).max}); + // To silence warning + unused; + + return Base64.encode(input); + } +} diff --git a/contracts/mocks/WithInit.sol b/contracts/mocks/WithInit.sol index 04c53936d..763566af2 100644 --- a/contracts/mocks/WithInit.sol +++ b/contracts/mocks/WithInit.sol @@ -142,6 +142,13 @@ contract AuthoritiyObserveIsConsumingUpgradeableWithInit is AuthoritiyObserveIsC __AuthoritiyObserveIsConsuming_init(); } } +import "./Base64DirtyUpgradeable.sol"; + +contract Base64DirtyUpgradeableWithInit is Base64DirtyUpgradeable { + constructor() payable initializer { + __Base64Dirty_init(); + } +} import "./CallReceiverMockUpgradeable.sol"; contract CallReceiverMockUpgradeableWithInit is CallReceiverMockUpgradeable { diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index d4ec2782b..26b4b6099 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit d4ec2782b724be9e56bf33aa91ff599df185c0b0 +Subproject commit 26b4b6099936fc785309f3da118ec8607b6716ed diff --git a/package-lock.json b/package-lock.json index 0f4f9f55e..cf544edf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openzeppelin-solidity", - "version": "4.9.2", + "version": "5.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openzeppelin-solidity", - "version": "4.9.2", + "version": "5.0.1", "license": "MIT", "devDependencies": { "@changesets/changelog-github": "^0.4.8", diff --git a/test/utils/Base64.test.js b/test/utils/Base64.test.js index dfff0b0d0..c5d7add0e 100644 --- a/test/utils/Base64.test.js +++ b/test/utils/Base64.test.js @@ -1,8 +1,9 @@ const { expect } = require('chai'); const Base64 = artifacts.require('$Base64'); +const Base64Dirty = artifacts.require('$Base64Dirty'); -contract('Strings', function () { +contract('Base64', function () { beforeEach(async function () { this.base64 = await Base64.new(); }); @@ -30,4 +31,13 @@ contract('Strings', function () { expect(await this.base64.$encode([])).to.equal(''); }); }); + + it('Encode reads beyond the input buffer into dirty memory', async function () { + const mock = await Base64Dirty.new(); + const buffer32 = Buffer.from(web3.utils.soliditySha3('example').replace(/0x/, ''), 'hex'); + const buffer31 = buffer32.slice(0, -2); + + expect(await mock.encode(buffer31)).to.equal(buffer31.toString('base64')); + expect(await mock.encode(buffer32)).to.equal(buffer32.toString('base64')); + }); });