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

Rework xsnap/src/object-inspect.js #4767

Merged
merged 7 commits into from
Apr 9, 2022
Merged

Rework xsnap/src/object-inspect.js #4767

merged 7 commits into from
Apr 9, 2022

Conversation

michaelfig
Copy link
Member

@michaelfig michaelfig commented Mar 8, 2022

closes: #2146
closes: #4814

Description

This is the next step toward improving the code used by xsnap to provide a working console.log.

Security Considerations

Improves the security of our xsnap process by providing a vetted shim that really has been vetted.

Documentation Considerations

Testing Considerations

@michaelfig michaelfig added the xsnap the XS execution tool label Mar 8, 2022
@michaelfig michaelfig requested review from dckc and erights March 8, 2022 19:57
@michaelfig michaelfig self-assigned this Mar 8, 2022
Copy link
Member

@dckc dckc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is how far I got before the dinner bell rang...

packages/xsnap/test/test-promise-reject.js Outdated Show resolved Hide resolved
Comment on lines 28 to 56
const mapSize = Object.getOwnPropertyDescriptor(Map.prototype, 'size').get;
const mapForEach = Map.prototype.forEach;
const setSize = Object.getOwnPropertyDescriptor(Set.prototype, 'size').get;
const setForEach = Set.prototype.forEach;
const weakMapHas = WeakMap.prototype.has;
const weakSetHas = WeakSet.prototype.has;
const weakRefDeref = WeakRef.prototype.deref;
const booleanValueOf = Boolean.prototype.valueOf;
const objectToString = Object.prototype.toString;
const functionToString = Function.prototype.toString;
const $match = String.prototype.match;
const $slice = String.prototype.slice;
const $replace = String.prototype.replace;
const $toUpperCase = String.prototype.toUpperCase;
const $test = RegExp.prototype.test;
const $concat = Array.prototype.concat;
const $join = Array.prototype.join;
const bigIntValueOf = BigInt.prototype.valueOf;
const getOwnPropertySymbols = Object.getOwnPropertySymbols;
const symToString = Symbol.prototype.toString;
// ie, `has-tostringtag/shams
const toStringTag = Symbol.toStringTag;
const isEnumerable = Object.prototype.propertyIsEnumerable;
const dateToISOString = Date.prototype.toISOString;

const gPO = Reflect.getPrototypeOf;
const hasOwn = Object.prototype.hasOwnProperty;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why alias all this stuff? I have some vague ideas, but I don't understand well enough to maintain this list. In particular, the mapSize and setSize contortions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to guard against some other vetted shim mutating one of the standard objects? I like defensive, but is that still in the threat model?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to guard against some other vetted shim mutating one of the standard objects? I like defensive, but is that still in the threat model?

This is a light refactoring of the original object-inspect.js code. I didn't rewrite it further because I didn't see the need to.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a locked down environment this shouldn't be necessary. If we do want to adopt this style for this file, maybe we could enable the eslint rule to disable polymorphic calls like the one endo uses: endojs/endo#827

packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
packages/xsnap/src/object-inspect.js Show resolved Hide resolved
@erights
Copy link
Member

erights commented Mar 10, 2022

@FUDCo , @michaelfig is here doing a permissive unprivileged object-quotation system much like I originally wanted to do for q (aka assert.quote). I didn't do that because, as far as I remember, you really wanted the output of q to be within JSON and eventually convinced me to do that. IIUC, you do not recall lobbying for that and are in fact indifferent to the JSON-ability of the q output. I'm just giving background. I don't care what really happened historically.

With that as background, could you confirm that, once this PR is merged, you would not object to q using its form of quotation?

@dckc
Copy link
Member

dckc commented Mar 10, 2022

How does test coverage for this version compare with what it replaces? Help me figure it out?

https://agoric-sdk-coverage.netlify.app/packages/xsnap/lib/object-inspect.js.html ?
70% Statements 378/540 49.75% Branches 103/207 77.14% Functions 27/35 70% Lines 378/540

@michaelfig
Copy link
Member Author

@erights, let's discuss how to review this:

@erights
Copy link
Member

erights commented Mar 10, 2022

is this okay as a vetted shim? Are we confident it doesn't reach for powerful things?

@michaelfig , Why would it need to be a vetted shim, rather than unprivileged code running under normal post-lockdown SES restrictions?

@michaelfig
Copy link
Member Author

is this okay as a vetted shim? Are we confident it doesn't reach for powerful things?

@michaelfig , Why would it need to be a vetted shim, rather than unprivileged code running under normal post-lockdown SES restrictions?

Sorry, my confusion. Yes, it's unprivileged code in the post-lockdown start compartment. I'd like ideally to run it in a compartment, but am unsure about how.

@erights
Copy link
Member

erights commented Mar 10, 2022

Yes, it's unprivileged code in the post-lockdown start compartment. I'd like ideally to run it in a compartment, but am unsure about how.

Excellent. Yes, let's find time to discuss. Thanks!

@dckc
Copy link
Member

dckc commented Mar 11, 2022

Sorry, my confusion. Yes, it's unprivileged code in the post-lockdown start compartment.

Really? It looks to me like we run this to build the console that we give to SES:

import './console-shim.js';
import '@endo/init';

import inspect from '../src/object-inspect.js';

@michaelfig
Copy link
Member Author

It looks to me like we run this to build the console that we give to SES:

Ahh, yes. The reason it's a vetted shim is because of initialisation ordering in SES, not because we actually need any of that power.

Instinctively, we should be able to relax that constraint and install the object inspector from a post-lockdown compartment.

@dckc
Copy link
Member

dckc commented Mar 11, 2022

... initialisation ordering in SES ...

Instinctively, we should be able to relax that constraint and install the object inspector from a post-lockdown compartment.

Yes, we used to have a lo-fi console arg quoter that we used for SES initialization, and then we replaced the lo-fi console quoter with assert.quote. I guess we don't want to go back to exactly that, but something analagous seems workable...

https://github.com/Agoric/agoric-sdk/blob/a3306d01d8e87c4bc7483a61e42cc30b006feb81/packages/xsnap/lib/console-shim.js
https://github.com/Agoric/agoric-sdk/blob/a3306d01d8e87c4bc7483a61e42cc30b006feb81/packages/xsnap/lib/ses-boot.js

@erights
Copy link
Member

erights commented Mar 11, 2022

Yes, we used to have a lo-fi console arg quoter that we used for SES initialization, and then we replaced the lo-fi console quoter with assert.quote. I guess we don't want to go back to exactly that, but something analagous seems workable...

https://github.com/Agoric/agoric-sdk/blob/a3306d01d8e87c4bc7483a61e42cc30b006feb81/packages/xsnap/lib/console-shim.js https://github.com/Agoric/agoric-sdk/blob/a3306d01d8e87c4bc7483a61e42cc30b006feb81/packages/xsnap/lib/ses-boot.js

TIL setQuote. I had no idea. Neat!

Copy link
Contributor

@FUDCo FUDCo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya shoor okay

Copy link
Member

@erights erights left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of minor nits but one genuine bug -- where an implementation limitation of the SES shim led you astray.

packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
packages/xsnap/test/test-inspect.js Show resolved Hide resolved
packages/xsnap/test/test-inspect.js Show resolved Hide resolved
packages/xsnap/test/test-inspect.js Show resolved Hide resolved
packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
Comment on lines 28 to 56
const mapSize = Object.getOwnPropertyDescriptor(Map.prototype, 'size').get;
const mapForEach = Map.prototype.forEach;
const setSize = Object.getOwnPropertyDescriptor(Set.prototype, 'size').get;
const setForEach = Set.prototype.forEach;
const weakMapHas = WeakMap.prototype.has;
const weakSetHas = WeakSet.prototype.has;
const weakRefDeref = WeakRef.prototype.deref;
const booleanValueOf = Boolean.prototype.valueOf;
const objectToString = Object.prototype.toString;
const functionToString = Function.prototype.toString;
const $match = String.prototype.match;
const $slice = String.prototype.slice;
const $replace = String.prototype.replace;
const $toUpperCase = String.prototype.toUpperCase;
const $test = RegExp.prototype.test;
const $concat = Array.prototype.concat;
const $join = Array.prototype.join;
const bigIntValueOf = BigInt.prototype.valueOf;
const getOwnPropertySymbols = Object.getOwnPropertySymbols;
const symToString = Symbol.prototype.toString;
// ie, `has-tostringtag/shams
const toStringTag = Symbol.toStringTag;
const isEnumerable = Object.prototype.propertyIsEnumerable;
const dateToISOString = Date.prototype.toISOString;

const gPO = Reflect.getPrototypeOf;
const hasOwn = Object.prototype.hasOwnProperty;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a locked down environment this shouldn't be necessary. If we do want to adopt this style for this file, maybe we could enable the eslint rule to disable polymorphic calls like the one endo uses: endojs/endo#827

packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
const gPO = Reflect.getPrototypeOf;
const hasOwn = Object.prototype.hasOwnProperty;

export default function inspect0(obj, opts = {}, depth = 0, seen = new Set()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: s/obj/val

Comment on lines 81 to 83
if (typeof depth === 'undefined') {
depth = 0;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're already setting a default above, aren't we?

const indent = getIndent(opts, depth);

if (seen.has(obj)) {
return '[Circular]';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't duplicated values can also trigger this. E.g.

const obj = {};

inspect0({foo: obj, bar: obj});

return false;
}
try {
weakRefDeref.call(x);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is sad as it forces the target to stay alive another turn

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything I should try to do about that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see another way. deref is the only thing that brand checks the [[WeakRefTarget]] slot.

remaining > 1 ? 's' : ''
}`;
return (
inspectString($slice.call(str, 0, opts.maxStringLength), opts) + trailer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically this can cut in the middle of a surrogate pair.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything I should do about that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We add a trailer which starts with a non surrogate char, so it may just technically be an invalid string, but all implementations handle malformed strings like that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhofman I don't see how that trailer helps. In the case in question, it would already be invalid as a unicode string.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what I meant is that this won't be concatenated with another string which may start with the second half of another surrogate pair, so if we split in the middle, the first half will just be detected as a broken pair, and processed as such by every algorithms which handles strings.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I understand now, and it is a valid concern.

packages/xsnap/src/object-inspect.js Show resolved Hide resolved
packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
continue;
}
if ($test.call(/[^\w$]/, key)) {
xs.push(`${inspect(key, obj)}: ${inspect(obj[key], obj)}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confused why obj is included in ${inspect(key, obj)} but is not in the symbol version below: ${inspect(syms[j])}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The symbol cannot possibly be the same as the object, so no need to check it for circularity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the symbol's value can refer to the object, can't it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the symbol's value can refer to the object, can't it?

As far as I read it, the inspect(syms[j]) inspects the symbol itself, not the symbol property's value.

@warner
Copy link
Member

warner commented Apr 2, 2022

y'all are reviewing this better than I could possibly do, I'll defer to you

@michaelfig michaelfig force-pushed the mfig-xsnap-logging branch 2 times, most recently from 116c1f7 to e1f4ff6 Compare April 9, 2022 00:49
Comment on lines +7 to +19
const src = objectInspectSources.replace(
/(^|\s)(export\s+default)(\s+)/g,
'$1/* $2 */$3',
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OMG. Definitely worth a comment to explain why.

Copy link
Member Author

@michaelfig michaelfig Apr 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely!

remaining > 1 ? 's' : ''
}`;
return (
inspectString($slice.call(str, 0, opts.maxStringLength), opts) + trailer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhofman I don't see how that trailer helps. In the case in question, it would already be invalid as a unicode string.

packages/xsnap/test/test-inspect.js Show resolved Hide resolved
const registered = symKeyFor(obj);
if (registered !== undefined) {
// Registered symbol.
return `Symbol.for(${registered})`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

packages/xsnap/src/object-inspect.js Outdated Show resolved Hide resolved
Copy link
Member

@erights erights left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@michaelfig michaelfig added the automerge:no-update (expert!) Automatically merge without updates label Apr 9, 2022
@mergify mergify bot merged commit fa068d1 into master Apr 9, 2022
@mergify mergify bot deleted the mfig-xsnap-logging branch April 9, 2022 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:no-update (expert!) Automatically merge without updates xsnap the XS execution tool
Projects
None yet
Development

Successfully merging this pull request may close these issues.

review or replace object-inspect.js used by xsnap console console API on XS: compatibility, security impact?
6 participants