Skip to content

Commit

Permalink
feat(ibc): validate remoteAddr string before making outbound connection
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Apr 25, 2024
1 parent d077d70 commit d2257ef
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 6 deletions.
6 changes: 5 additions & 1 deletion packages/network/src/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,11 +497,15 @@ const preparePort = (zone, powers) => {
makeIncapable()
),
) {
const { revoked, localAddr, protocolImpl } = this.state;
const { revoked, localAddr, protocolImpl, protocolHandler } =
this.state;

!revoked || Fail`Port ${localAddr} is revoked`;
/** @type {Endpoint} */
const dst = harden(remotePort);
await E(
/** @type {Remote<Required<ProtocolHandler>>} */ (protocolHandler),
).validateRemoteAddr(dst);
return watch(
E(protocolImpl).outbound(this.facets.port, dst, connectionHandler),
this.facets.portConnectWatcher,
Expand Down
2 changes: 2 additions & 0 deletions packages/network/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@
* p: Remote<ProtocolHandler>,
* ) => PromiseVow<void>} onRevoke
* The port is being completely destroyed
* @property {(remoteAddr: string) => Promise<boolean>} validateRemoteAddr
* Function to validate a remoteAddr is valid before attempting to connect to it.
*
* @typedef {object} InboundAttempt An inbound connection attempt
* @property {(desc: AttemptDescription) => PromiseVow<Connection>} accept
Expand Down
6 changes: 3 additions & 3 deletions packages/orchestration/test/utils/address.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from '@endo/ses-ava/prepare-endo.js';
import { decodeRemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js';
import { validateRemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js';
import {
makeICAChannelAddress,
makeICQChannelAddress,
Expand Down Expand Up @@ -88,7 +88,7 @@ test('makeICQChannelAddress', t => {
);
t.throws(
() =>
decodeRemoteIbcAddress(
validateRemoteIbcAddress(
makeICQChannelAddress('connection-0', {
version: 'ic/q-/2',
}),
Expand All @@ -97,6 +97,6 @@ test('makeICQChannelAddress', t => {
message:
/must be '\(\/ibc-hop\/CONNECTION\)\*\/ibc-port\/PORT\/\(ordered\|unordered\)\/VERSION'/,
},
'makeICQChannelAddress not hardened against malformed version. use `decodeRemoteIbcAddress` to detect this',
'makeICQChannelAddress not hardened against malformed version. use `validateRemoteIbcAddress` to detect this, or expect IBC ProtocolImpl to throw',
);
});
8 changes: 8 additions & 0 deletions packages/vats/src/ibc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
decodeRemoteIbcAddress,
encodeLocalIbcAddress,
encodeRemoteIbcAddress,
validateRemoteIbcAddress,
} from '../tools/ibc-utils.js';

/** @import {LocalIbcAddress, RemoteIbcAddress} from '../tools/ibc-utils.js' */
Expand Down Expand Up @@ -243,6 +244,13 @@ export const prepareIBCProtocol = (zone, powers) => {
this.state.lastPortID += 1n;
return `port-${this.state.lastPortID}`;
},
/**
* @param {string} remoteAddr
* @returns {Promise<boolean>}
*/
async validateRemoteAddr(remoteAddr) {
return validateRemoteIbcAddress(remoteAddr);
},
/** @type {Required<ProtocolHandler>['onBind']} */
async onBind(_port, localAddr) {
const { util } = this.facets;
Expand Down
14 changes: 14 additions & 0 deletions packages/vats/test/test-network.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@ test('network - ibc', async t => {

await testEcho();

const testInvalidRemoteAddr = async () => {
await E(p).addListener(makeIBCListener({ publisher }));
t.log('Connecting to a invalid remoteAddr throws');
await t.throwsAsync(
when(E(p).connect('/ibc-port/port-1/unordered/foo/f/f/f/f')),
{
message:
/must be '\(\/ibc-hop\/CONNECTION\)\*\/ibc-port\/PORT\/\(ordered\|unordered\)\/VERSION'/,
},
);
};

await testInvalidRemoteAddr();

const testIBCOutbound = async () => {
t.log('Connecting to a Remote Port');
const [hopName, portName, version] = ['connection-11', 'port-98', 'bar'];
Expand Down
30 changes: 28 additions & 2 deletions packages/vats/tools/ibc-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,41 @@ harden(REMOTE_ADDR_RE);
export const LOCAL_ADDR_RE = /^\/ibc-port\/(?<portID>[-a-zA-Z0-9._+#[\]<>]+)$/;
/** @typedef {`/ibc-port/${string}`} LocalIbcAddress */

/** @param {string} remoteAddr */
export const decodeRemoteIbcAddress = remoteAddr => {
/**
* @overload
* @param {string} remoteAddr
* @param {undefined | false} [returnMatch]
* @returns {boolean}
*/
/**
* @overload
* @param {string} remoteAddr
* @param {true} returnMatch
* @returns {RegExpMatchArray}
*/
/**
* Validates a remote IBC address format and returns true if the address is
* valid.
*
* @param {string} remoteAddr
* @param {boolean} [returnMatch]
*/
export const validateRemoteIbcAddress = (remoteAddr, returnMatch = false) => {
const match = remoteAddr.match(REMOTE_ADDR_RE);
// .groups is to inform TS https://github.com/microsoft/TypeScript/issues/32098
if (!(match && match.groups)) {
throw TypeError(
`Remote address ${remoteAddr} must be '(/ibc-hop/CONNECTION)*/ibc-port/PORT/(ordered|unordered)/VERSION'`,
);
}
return returnMatch ? match : true;
};

/** @param {string} remoteAddr */
export const decodeRemoteIbcAddress = remoteAddr => {
const match = validateRemoteIbcAddress(remoteAddr, true);
if (!match.groups)
throw Error('Unexpected error, validateRemoteIbcAddress should throw.');

/** @type {import('../src/types.js').IBCConnectionID[]} */
const hops = [];
Expand Down

0 comments on commit d2257ef

Please sign in to comment.