@@toStringTag spoofing for null and undefined

Jordan Harband ljharb at gmail.com
Tue Jan 20 18:42:56 PST 2015


(Allen, I'm not sure if you saw my other reply:
https://esdiscuss.org/topic/tostringtag-spoofing-for-null-and-undefined#content-10
)

In debugging, it's been awhile since I've seen anyone use Object#toString
output - that's surprising to me as a motivation, and since it's always
been pretty useless, I'd be surprised if anyone expected to be able to use
it for that purpose in the future.

Brand checking is done because it's deemed necessary - it is absolutely
critical to me that I have a way to determine "is this likely to behave
like an array" without ducktyping or feature testing. The fact that someone
could make a poison array doesn't bother me - and if I want to prevent
Array from being poisoned I can do that myself - I'm concerned about people
passing me things and *not realizing* that it's not what I wanted them to
pass.

Your #2 says that "[[Class]] is gone because it did not provide an
extensible way to do brand checking" - without the changes I've proposed
(nonconfigurable @@toStringTag on builtins + unspoofable builtin
@@toStringTag values), there's simply no way to do reliable brand checking
at all. Regardless of whether you believe that brand checking is a good
thing, in ES5 and prior, it exists and is in fact reliable (to some degree
of reliable). I think that the value of "it produces nicer debug output for
developers who don't use a debugger" does not warrant the loss of value of
"somewhat reliable cross-realm brand checking".

I'd certainly prefer removing @@toStringTag entirely over the current state
of it in ES6. That said, I think there's lots of value in providing a
language-accessible means of previously magic language behavior, and I'd
hate to lose that too.

On Tue, Jan 20, 2015 at 3:51 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>
wrote:

>
> On Jan 20, 2015, at 11:38 AM, Jordan Harband wrote:
>
> "X is bad" is, quite frankly, mostly irrelevant here. When X is bad in JS,
> then TC39 seems to have gone with primarily one of two choices: use strict
> mode to remove it, or, provide a better option Y so that developers *want*
> to move away from X and start using Y. In this case, the value of type
> checking doesn't matter - people do it, and so it must be considered.
>
> There are two primary usages of Object.prototype.toString in my eyes:
>  - constructs like `Object.prototype.toString.call(new String('foo')) ===
> Object.prototype.toString.call('foo')` (or using that to throw an explicit
> error "don't use object forms of primitives") which ensures that people
> using my code won't fall into the very common footgun (thanks, Java) of
> using boxed primitives. (The same issue will apply to Symbols in ES6, but
> at least I can do `typeof Symbol.prototype.toString.call(value) ===
> 'symbol'`)
>  - Testing of values from other realms (namely iframes).
> `Object.prototype.toString.call(value) === '[object Array]'` is a
> remarkably consistently written return value of an `Array.isArray`
> polyfill, since it works no matter how somebody has monkeyed with any
> global Array object.
>
> Certainly it is trivial to construct a malicious array, for example, and
> pass that around breaking all sorts of things. The goal, in my opinion, of
> Object.prototype.toString checking is *not* security - it's avoiding common
> developer hazards. In other words, I want my code to fail fast when the
> developer unintentionally passes me the wrong thing - something that I
> think we can agree happens often.
>
> 1) configurability of @@toStringTag
> It appears that TC39 considers it important to not break existing JS code
> with spec changes. Thus, `Object.prototype.toString.call(foo)`, for any ES5
> value "foo", must always and forever return the same value that it returned
> in ES5 - otherwise, existing code may follow different code paths in ES5
> versus ES6, which is a hazard. This leads me to the belief that
> @@toStringTag values on ES5 builtins should never be changeable.
>
> Some have replied to this, "if you don't run first, all bets are off -
> freeze it if you want it". Fair point! If you don't run first and keep a
> reference to Object.prototype.toString, you're screwed anyways. However, in
> ES5, if I *do* run first, I have 100% opt-in protection against somebody
> breaking things I care about. Essentially all polyfilled `Array.isArray`
> code on the web could break if it is possible to redefine
> `Array.prototype[@@toStringTag]` (in any realm, not just the one I start
> in), which is currently possible by default in ES6.
>
> I believe that all built-in objects, in all realms, should have a
> nonconfigurable @@toStringTag for this reason.
>
> 2) Since people *are* doing this type checking, if I make an object that
> defines its @@toStringTag value to return "Array", I will break code that
> does this. (Hence the protections in
> https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring)
> I've filed https://bugs.ecmascript.org/show_bug.cgi?id=3506 to discuss
> the missing values (namely Math, JSON, and Object, and Null, and Undefined
> have been mentioned as well).
>
> Currently, the spec handles this by including a whitelist of values, and
> specifying that any value not in this list receives a "~" prefix. (The
> value of the prefix itself is irrelevant, let's please not bikeshed it).
>
> My proposal is that rather than maintain a whitelist, and have that added
> complexity, that ES6 specifies that any user-defined @@toStringTag value
> will *always* and unconditionally have a prefix applied.
>
> --------------------
>
>
> I believe that either of these proposals by themselves will be a win - but
> with both together, "nominal type checking" / branding code will continue
> to work, and there will be no hazards or footguns by default.
>
> For those who dislike this kind of code, I challenge you and the committee
> to finalize and publish a better approach for answering the question "does
> this behave like an array" or "does this behave like a map" that work
> cross-realm (besides exhaustive duck-typing and/or feature detection),
> rather than attempting to simply oppress what many consider to be a valid
> approach, and a functional and existing one.
>
>
> there are two things going on with @@toStringTag and  ES6 O.p.toString
>
> 1)  @@toStringTag is primarily intended as an oopen-ended extension point
> that allows JS class (and other abstraction)  to parameterize the
> descriptive string produced by O.p.toString.  This parameterization is
> desirable (as an alternative or supplement to over-riding toString) in
> support of (usually debugging) use cases where O.p.toString called as a
> reliable means to get a displayable "[  ]" description of an object.
>
> For that purpose, we don't really care whether an object's @@toStringTag
> is stable or even whether it spoofs one of the legacy tag values. It's just
> a hook for plugging into that extension point for that purpose.
>
> 2) [[Class]] is gone because it did not provide an extensible way to do
> brand checking yet it was (indirectly through O.p.toString) being used in
> that way. O.p.toString never provided a way to do brand checking on values
> defined using JS definable constructors. When and if we decide that we can
> no longer live without such an extensible branding mechanism we will define
> one, with its one mechanism and interface.  We wouldn't misuse O.p.toString
> for that purpose.
>
> If developers started to use @@toStringTag as a brand that they access
> using O.p.toString (and we start making accommodations in support of that
> usage) we'd be worse off WRT branding then we are with ES5.
>
> I would sooner see @@toStringTag go completely way than see it start to be
> used as a brand.  If that is how we really think it will go down, then I
> say we should screw the convenience that we were trying to offer with #1
> above  and completely revert to what is essential ES5 behavior.  In therms
> of the current spec. that means anything allocated by a ES defined
> constructor would answer "[object Object]".
>
> As I said in a tweet yesterday: Our dilemma: some things that made sense
> in the past & people are used to, don't make future sense
>
> Using, O.p.toString is one of those things. If improving the "produce a
> debug string" behavior is going to reenforce a O.p.toString usage that
> doesn't make sense for the future (or even for ES6), then we probably
> shouldn't improve O.p.toString.
>
> (But one remaining problem, if we lost @@toStringTag then there isn't any
> particularly good way to explain the behavior of it when applied to DOM
> objects.)
>
>    -
>
>
>
> allen
>
>
>
>
>
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20150120/908e2b05/attachment.html>


More information about the es-discuss mailing list