Closed Bug 309122 Opened 19 years ago Closed 16 years ago

Crash re-instantiating Java plugin rapidly [@ MRJContext::synchronizeClipping] [@ SetEmptyRgn]

Categories

(Core Graveyard :: Java: OJI, defect)

PowerPC
macOS
defect
Not set
critical

Tracking

(Not tracked)

RESOLVED WORKSFORME

People

(Reporter: jruderman, Assigned: smichaud)

References

()

Details

(Keywords: crash, testcase)

Crash Data

Attachments

(1 file)

Steps to reproduce:
1. Go to http://www.squarefree.com/spampede/evil.html.
2. Click the "Try to crash" button at the top.

Result: Firefox will either hang or crash [@ SetEmptyRgn], with about equal
probability.


Firefox build: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.9a1)
Gecko/20050918 Firefox/1.6a1

Java version used by Firefox: 1.4.2_09 from Apple

Mac OS X version: 10.4.2
Thread 0 Crashed:

com.apple.QD
0  SetEmptyRgn + 24

com.netscape.MRJPlugin
1  MRJContext::synchronizeClipping() + 296
2  MRJContext::setWindow(nsPluginWindow*) + 428
3  MRJPluginInstance::SetWindow(nsPluginWindow*) + 88

org.mozilla.firefox
4  MOZ_Z__length_code + 197856
5  nsPluginHostImpl::InstantiateEmbeddedPlugin(char const*, nsIURI*,
nsIPluginInstanceOwner*) + 2152
6  nsObjectFrame::InstantiatePlugin(nsPresContext*, nsHTMLReflowMetrics&,
nsHTMLReflowState const&, nsIPluginHost*, char const*, nsIURI*) + 908
7  nsObjectFrame::Reflow(nsPresContext*, nsHTMLReflowMetrics&,
nsHTMLReflowState const&, unsigned&) + 2024
I get crashes and hangs (after clicking "Try to crash") with all the
Mozilla.org browsers and JEP versions that I tried.  I also got crashes
without the JEP (using Java 1.3.1 via Apple's Java Applet.plugin).

The crashes were all over the map, though they were ususally in JVM code.  I
never saw a crash in QD's SetEmptyRgn().

All my tests were on OS X 10.4.2.  But I'm sure these results (or something
like them) could be replicated on OS X 10.3.X and 10.2.8.

Safari didn't crash.  Mozilla 1.7.11 with Sun's Java 1.4.2_08 did't crash on
SuSE Linux 9.2.

I'm not sure there's anything I can do about this.  I think it has something
to do with the fact that the Mozilla.org browsers (using the OJI) expect an
applet to be created synchronously -- a call is made to
nsIPluginInstance::setWindow(), and an applet is just supposed to be up and
running by the time it returns.  But in Apple's implementations of Sun's
"recent" JVMs, most important procedures (like starting the JVM or loading an
applet) take place asynchronously -- especially in Java 1.4.X and 1.5, but
even (to some extent) in Java 1.3.1.  There's lots of code in
JavaEmbeddingPlugin.bundle (and even some in MRJPlugin.plugin) that tries to
compensate for this (by for example waiting for a certain stage to be reached
in loading an applet (while processing NSRunLoop and CFRunLoop events on the
main thread) before returning from the browser's call to setWindow()).  But my
ability to compensate is never going to be perfect.

I suspect that the reason Safari doesn't crash is that it starts the JVM and
loads applets asynchronously -- i.e. it starts these procedures in a new
thread and uses a callback to find out when they've finished.  But I don't
know why Mozilla 1.7.11 on Linux with Sun's Java 1.4.2_08 doesn't crash.
Maybe someone who knows more about Sun's code can enlighten us.

I should add that this test case (the "Try to crash" button) isn't a real
world example -- I don't think that applets will ever be instantiated (and
deinstantiated) so rapidly in normal usage.

I've tried hard to make the JEP behave correctly when switching rapidly (using
the back and forward buttons) between three or four browser pages, each of
which contains lots of applets.

Examples of such pages can be found at the following URLs.

http://www.mozilla.org/quality/browser/front-end/testcases/oji/tagstest.html
http://brittnysseafood.com/

The applets at brittnysseafood are the most fiendish torture I've yet found
for the JEP :-)

This may not be real-world behavior, but crashes on these kinds of evil pages
make it difficult for me to test for a class of security holes (see bug 306663).
Executing the testcase with Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US;
rv:1.8b4) Gecko/20050916 SeaMonkey/1.0a with Java 1.5.0_02 the memory
consumption rises asymptotical up to 150 MB. After closing the window with the
testcase, SeaMonkey is partially freezed (e.g. no autofocus to address bar after
opening new tab, no autocompletion to URIs etc.) and sometimes can't be quit
without killing the process in task manager.

Another oddity is, that the testcase can't be executed a second time when above
point is reached.
Assignee: yuanyi21 → pete.zha
I didn't think there was anything I could do about this ... but I've
now found a "non-real world" setting (jep.asyncinit) for the Java
Embedding Plugin that allows these "non-real world" tests to run
pretty much crash-free -- at least on Tiger (OS X 10.4.X).

This setting makes the JEP do applet instantiation somewhat more
asynchronously.  But it's _not_ something that I recommend to ordinary
users -- it degrades the JEP's functionality (for example it becomes
impossible to call an applet's methods from a JavaScript onload
handler, which is something that many online banking sites need to
do).

To turn jep.asyncinit on, add the following to the Java Control
Panel's Java Runtime Parameters box:

    -Djep.asyncinit=true

I've found that (with this setting) Mozilla 1.7.11 and Firefox 1.0.6
rarely crash at all, and other Mozilla.org browsers crash only when
you try to quit the browser or close the window containing the
rapidly-reloading applet.

Needless to say, all this tends to confirm what I said in comment #2
about the underlying cause of this problem.
> The crashes were all over the map, though they were ususally in JVM code.  I
> never saw a crash in QD's SetEmptyRgn().

I later did some tests on Mac OS X 10.3.9 (with jep.asyncinit turned on), and
did see some examples of your crash in QuickDraw's SetEmptyRgn() (called from
MRJPlugin.plugin).  These must have resulted from trying to access MRJContext
objects after they had been deleted, which must (in turn) have been caused by
the browser making calls to nsIPluginInstance::setWindow() on a plugin
instance after having already called nsIPluginInstance::destroy() on the same
instance.

I already had code in place (in the MRJ Plugin) to deal with this kind of
eventuality.  I've tightened this code up, and the crashes in SetEmptyRgn()
seem to have disappeared.  The improved code appears in version 0.9.4+a of the
Java Embedding Plugin (a so-called "nightly"), which I've just released:

http://javaplugin.sourceforge.net/

Chris - can you help us get this bug better assigned?
If I understand things right smichaud is a good owner, and we just need to take a drop in the mozilla tre from http://javaplugin.sourceforge.net/  when the nightlies that smichaud has tested all check out; then the fix will show up camino and firefox builds.

smichaud, asa, others,  does that sound right?

chris h.
Assignee: pete.zha → smichaud
This problem is, practically speaking, insoluble.  For more information see
comment #2.

That said, I've actually been able to use Jesse's "evil" applet to track down
and fix problems in the MRJ Plugin JEP (see comment #7) and
JavaEmbeddingPlugin.bundle.  These changes have appeared in subsequent JEP
versions ... though most of them are already present in JEP 0.9.5 (what's
bundled with Firefox 1.5).  (The latest JEP version is 0.9.5+b.)

However, you need to be aware (and I'm repeating this from earlier comments)
that the "evil" applet (in my experience) only provides useful results if you
set "jep.anyncinit" to "true" -- otherwise you get crashes all over the map.
And this setting isn't compatible with everyday use of the JEP (see comment
#6).

I'd like to leave this problem open but downgrade it from "critical" to
"minor".  Is that OK with everyone?

Status: NEW → ASSIGNED
Firefox trunk, which comes with JEP 0.9.5+b, usually becomes unresponsive and crashes if I try to quit.  I don't need to force quit.  Example crash:

  Thread 23 Crashed:
  0    _class_lookupMethodAndLoadCache + 376
  1    objc_msgSend + 244
  2    Java_MyCAppletFrame_getViewPtr + 35412
  3    Java_MyCAppletFrame_getViewPtr + 34624
  4    forkThreadForFunction + 108
  5    _pthread_body + 96

Firefox 1.5, which comes with JEP 0.9.5, behaves similarly.  Example crash:

  Thread 20 Crashed:
  0    objc_msgSend + 32
  1    Java_MyCAppletFrame_getViewPtr + 35412
  2    Java_MyCAppletFrame_getViewPtr + 33444
  3    Java_MyCAppletFrame_getViewPtr + 34216
  4    forkThreadForFunction + 108
  5    _pthread_body + 96

I also saw Firefox 1.5 crash in SetEmptyRgn once.  Is it just a bug that Gecko calls nsIPluginInstance::setWindow() on a plugin
instance after having already called nsIPluginInstance::destroy() on the same
instance (comment 7), or is that a result of the same design decisions that make it difficult to fix the other crashes?

Are any of these crashes likely to be exploitable, or do they crash in predictable, safe ways (e.g. reading memory address zero)?
Did you set jep.asyncinit to true before you did this test?

If not, then your crashes may not mean a whole lot -- as I pointed out in
comment #2 and comment #10.

Whenever I tried your "evil" applet without this setting, I got crashes all
over the place.

That said, the particular traces you've given look like attempts to send
Objective C "messages" to objects that have been deleted.

> That said, the particular traces you've given look like attempts to send
> Objective C "messages" to objects that have been deleted.

My guess would be that this kind of error isn't exploitable.

> I also saw Firefox 1.5 crash in SetEmptyRgn once.  Is it just a bug that
> Gecko calls nsIPluginInstance::setWindow() on a plugin instance after having
> already called nsIPluginInstance::destroy() on the same instance (comment
> 7), or is that a result of the same design decisions that make it difficult
> to fix the other crashes?

As I've already said in comment #7, I saw this crash pretty consistently on OS
X 10.3.9 with jep.asyncinit set to true, in a (now) old version of the JEP.
It was due to Gecko calling nsIPluginInstance::setWindow() on a deleted
object, and I've now worked around the problem.

> This problem is, practically speaking, insoluble.  For more information see
> comment #2.

The problem _would_ be soluble if mozilla.org were willing to change how its
browsers launch applets on OS X -- for example waiting for an "applet loaded"
callback before trying to use a Java applet.

is this mac only?

we should be able to avoid calling stuff in destroyed objects w/o needing async plugin instation. of course, if it's not mozilla which does that, then this isn't really mozilla's fault...
> is this mac only?

Yes, I believe so.

> we should be able to avoid calling stuff in destroyed objects w/o needing
> async plugin instation ...

Asynchronous loading (the "applet loaded callback") isn't aimed at solving
this problem (which in any case is pretty easily worked around), but at making
Mozilla.org browsers compatible with how Apple's recent JVMs (1.4.X and 5.0)
load applets.

These newer JVMs need to load applets on a secondary thread.  And (of course)
there might be several seconds' delay before an applet is loaded and
initialized.  But mozilla.org browsers currently expect a Java applet to be
fully loaded and initialized by the time a call (on the main thread) to
nsIPluginInstance::setWindow() (the one which triggers loading the applet)
returns.  The Java Embedding Plugin currently deals with this by processing
events on the main thread (in its own event loop) until the applet seems to be
ready, and only then allowing the relevant call to
nsIPluginInstance::setWindow() to return.

This works fine in ordinary circumstances.  But it goes haywire when applets
are loaded and destroyed too rapidly.  It seems that the JEP's event loop
processing falls behind when this happens, leading to massive numbers of calls
to objects that have been deleted (and not just in JEP code).

But the JEP's event loop processing would be unnecessary if mozilla.org
browsers didn't expect a Java applet to be fully initialized after
nsIPluginInstance::setWindow() returned -- e.g. if it they supported an
"applet loaded" or "applet initialized" callback.

For another discussion of these issues, see:

https://bugzilla.mozilla.org/show_bug.cgi?id=311530#c2
so er, why is it not sufficient to just cache the arguments of the last SetWindow call and pass them on to java whenever it's ready?
The arguments to SetWindow don't matter in this case (they're basically
irrelevant).

What does matter is that an applet gets launched whenever a call to SetWindow
is made whose "nsIPluginWindow *pluginWindow" argument is non-NULL and the
plugin's applet hasn't yet been launched.

See MRJContext::setWindow() in the MRJ Plugin JEP's
plugin/oji/MRJCarbon/plugin/Source/MRJContext.cp.

The same code (or or less) is also present in the original MRJ Plugin.
And even if the arguments to SetWindow were relevant, caching them wouldn't
help.  The problem won't be solved by delaying launching the applet.

> And even if the arguments to SetWindow were relevant, caching them wouldn't
> help.  The problem won't be solved by delaying launching the applet.

To clarify a bit, the applet needs to get its parameters as it's launched.

And any case, the problem isn't that the applet has insufficient information.
It's that the browser tries to use it before it's ready (particularly in the
case of the JavaScript OnLoad handler calling an applet's methods, which I
talk about at bug 311530).

Steven, what are the relevant calls in Gecko that are expecting stuff to be initialized before it has been?
> Steven, what are the relevant calls in Gecko that are expecting stuff to be
> initialized before it has been?

As I've said, a clear-cut example of this problem is the case where an
applet's methods are called from a JavaScript OnLoad handler (as mentioned in
comment #22 above and bug 311538):  If "jep.asyncinit" is set to "true",
you'll get an error that the method you're trying to call doesn't exist.  (Of
course this doesn't happen when you don't set "jep.asyncinit", or when you set
it to "false" -- this leaves in place the workaround I described in comment
#18.)

I don't know where in mozilla.org browser code the JavaScript OnLoad handler
is implemented.

> (as mentioned in comment #22 above and bug 311538)

Oops, I should have said "bug 311530".
We can block onload handler firing on applet init if you can communicate back to layout that init is not done yet, and when it completes.  Alternately, if the JEP has access to an nsIDocument it can do so itself...
> We can block onload handler firing on applet init if you can communicate
> back to layout that init is not done yet, and when it completes.

Currently the JEP C/C++ API supports three callbacks:  appletCreatedCallback
(usually called on a secondary thread), appletInitializedCallback (currently
always called on a secondary thread) and appletStartedCallback (currently
always called on the main thread).  (For more info see JavaEmbeddingPlugin.h.)

What code might I put in an appletInitializedCallback?  Would it be alright to
make these calls (whatever they are) on a secondary thread?  (If the answer is
"no", I could "schedule" a block of code to be called on the main thread
... though that could mean that the code doesn't get run as soon as it
should.)

Whatever objects and methods get used here, they should be accessible
(directly or indirectly) via calls to the nsIServiceManager interface.

> Alternately, if the JEP has access to an nsIDocument it can do so itself...

I'm pretty sure the JEP (i.e. the MRJ Plugin JEP) doesn't have access to this.

Hmmm...  So what we need is a way to get to the right document (the one the applet is for).  Or at least get to the right nsIPluginTagInfo(2?) or something.  That can find a document, and we could add an interface to it that has methods you could call to block and unblock onload.
> Or at least get to the right nsIPluginTagInfo(2?) or something.

I didn't know about this ... thanks!

You can get it from an nsIPluginInstancePeer instance, a pointer to which is
provided as a parameter to nsIPluginInstance::Initialize() (which is already
implemented in MRJPlugin.cpp).

> That can find a document,

nsIPluginTagInfo2 has an nsIDOMElement attribute.  Is this what you're talking
about?

> and we could add an interface to it that has methods you could call to block
> and unblock onload.

This would (presumably) take care of the onload handler problem.

But there are also more subtle manifestations of the "browser uses applet
before it's ready" problem.  One example is an applet which (like the NOAA
radar looping applets) updates the screen during its init() method
(i.e. before initialization has been completed) -- the NOAA radar looping
applet downloads its "maps" in its init() method, and draws each one as it has
finished downloading.  The JEP handles this correctly with its "synchronizing"
workaround in place.  But if "jep.asyncinit" is set to "true", no map gets
drawn until the applet's init() method has finished.

I'm not entirely sure why this happens.  I'm also quite sure I could find
other examples of mozilla.org browsers wanting to use a Java applet before
it's ready on OS X with the JEP and "jep.asyncinit" set to "true".

Nonetheless, I think it's worthwhile trying to find a solution to the onload
handler problem, even if our solution won't resolve the whole class of
"browser uses applet before it's ready" problems.  As best I can tell, the
onload handler problem is the most glaring (and serious) of the lot.

> nsIPluginTagInfo2 has an nsIDOMElement attribute.  Is this what you're talking
> about?

More or less.  It can get to the document via the element.  I'd rather add methods on an nsIPluginTagInfo3 interface of some sort than make JEP depend on nsIDocument.  ;)
Keywords: testcase
Blocks: 353557
No longer blocks: 353557
Is this bug something that should be fixed in the JEP (and thus should live in Core:Java Embedding Plugin) or somewhere else in Gecko (or perhaps both)?
WFM, Mac trunk debug + Mac OS X 10.4.11.  I don't see a crash or hang.  I do see some assertions, but I think they're covered in other bug reports.

###!!! ASSERTION: wrong thread: 'NS_IsMainThread()', file /Users/jruderman/trunk/mozilla/netwerk/base/src/nsIOService.cpp, line 477

###!!! ASSERTION: nsCookieService not thread-safe: '_mOwningThread.GetThread() == PR_GetCurrentThread()', file /Users/jruderman/trunk/mozilla/netwerk/cookie/src/nsCookieService.cpp, line 405

###!!! ASSERTION: nsPluginHostImpl not thread-safe: '_mOwningThread.GetThread() == PR_GetCurrentThread()', file /Users/jruderman/trunk/mozilla/modules/plugin/base/src/nsPluginHostImpl.cpp, line 2718
Status: ASSIGNED → RESOLVED
Closed: 16 years ago
Resolution: --- → WORKSFORME
Flags: in-testsuite?
Product: Core → Core Graveyard
Crash Signature: [@ MRJContext::synchronizeClipping] [@ SetEmptyRgn]
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: