Description

XWikiMessageTool has a context stored in it's data structure and uses that context when reading data from the wiki.

In scheduler jobs or background threads (watchlist, extension manager, lucene, or user scheduler jobs) the context is cloned from the initial context that is passed to the initializations of the threads. If this context is from a child wiki (in multi wiki) then the message tool might read the preferences with a context from the wrong wiki while the ExecutionContext might be from another wiki. This can lead to reading the preferences from the wrong wiki or even worse lead to preferences read without objects because of a comparaison of references done in the loadXWikiDoc code. The preferences object will then be wrong in memeory and might be subsequently saved back to the wiki without the preferences object.

The context is stored into the XWikiMessageTool object initially added to the context.

If you are in a background thread or a scheduler job you use a cloned Context.
Context cloning is the following which does not reset XWikiMessageTool and the XWikiMessageTool will stay in that cloned context. However the cloned context is not the one in the XWikiMessageTool object.

The getDefaultLanguage call will need to read the preferences and these can be read with a context being from one wiki while the context in the Execution context might be from another wiki.

Now in loadXWikiDoc there is a reference check between the object and the document. However these references are not generated using the same context. One is generated with the context from the MessageTool while the other is generated using the execution context. This leads to this check to fail and the objects will not be loaded. Additionally the preferneces objet read is not the one the MessageTool wanted to load but one from another wiki.

Note: this check has been put in loadXWikiDoc, because mysql with certain configuration is doing case insensitive comparison in queries leading to potentially associating objects with the wrong document (suppose you have Test.MyDoc and Test.mydoc which both have Tag Objects. Then you could have the tags of both documents loading with both documents). Now this check could not include the wiki name in it but this would partially fix the issue as the document would still be a document from the wrong wiki or other problems might occur.

// It seems to search before is case insensitive. And this would break the loading if we get an
// object which doesn't really belong to this document
if (!object.getDocumentReference().equals(doc.getDocumentReference())) {
continue;
}

The core issue here is that the XWiki context object stored in MessageTool and the one in the ExecutionContext are different and this should not happen. When reading documents this can lead to very bad things.

It could be usefull to check that the context parameter in loadXWikiDoc is the same as the context object in the Execution context and issue a serious warning when this happens.

Ludovic Dubost
added a comment - 04/Mar/13 10:57 - edited The following code initializes MessageTool
in XWikiContext
public XWikiMessageTool getMessageTool()
{
XWikiMessageTool msg = ((XWikiMessageTool) get( "msg" ));
if (msg == null ) {
getWiki().prepareResources( this );
msg = ((XWikiMessageTool) get( "msg" ));
}
return msg;
}
in XWiki.java
public void prepareResources(XWikiContext context)
{
if (context.get( "msg" ) == null ) {
// String ilanguage = getInterfaceLanguagePreference(context);
String dlanguage = getLanguagePreference(context);
Locale locale = new Locale(dlanguage);
context.put( "locale" , locale);
if (context.getResponse() != null ) {
context.getResponse().setLocale(locale);
}
ResourceBundle bundle = ResourceBundle.getBundle( "ApplicationResources" , locale);
if (bundle == null ) {
bundle = ResourceBundle.getBundle( "ApplicationResources" );
}
XWikiMessageTool msg =
new XWikiMessageTool(Utils.getComponent(LocalizationManager.class), Utils.getComponentManager(),
context);
context.put( "msg" , msg);
VelocityContext vcontext = ((VelocityContext) context.get( "vcontext" ));
if (vcontext != null ) {
vcontext.put( "msg" , msg);
vcontext.put( "locale" , locale);
}
@SuppressWarnings( "unchecked" )
Map< String , Object > gcontext = (Map< String , Object >) context.get( "gcontext" );
if (gcontext != null ) {
gcontext.put( "msg" , msg);
gcontext.put( "locale" , locale);
}
}
}
In XWikiMessageTool
public XWikiMessageTool(ResourceBundle bundle, XWikiContext context)
{
this .bundle = bundle;
this .context = context;
}
The context is stored into the XWikiMessageTool object initially added to the context.
If you are in a background thread or a scheduler job you use a cloned Context.
Context cloning is the following which does not reset XWikiMessageTool and the XWikiMessageTool will stay in that cloned context. However the cloned context is not the one in the XWikiMessageTool object.
@Override
public synchronized XWikiContext clone()
{
XWikiContext context = (XWikiContext) super .clone();
// Make sure to have unique instances of the various caches
context.displayedFields = Collections.synchronizedList( new ArrayList< String >( this .displayedFields));
context.classCache = Collections.synchronizedMap( new LRUMap( this .classCacheSize));
return context;
}
When the message tool is called the context stored is used, although in the mean time the database or other things might have been changed in the cloned context.
public List<XWikiDocument> getDocumentBundles()
{
String defaultLanguage = this .context.getWiki().getDefaultLanguage( this .context);
The getDefaultLanguage call will need to read the preferences and these can be read with a context being from one wiki while the context in the Execution context might be from another wiki.
Now in loadXWikiDoc there is a reference check between the object and the document. However these references are not generated using the same context. One is generated with the context from the MessageTool while the other is generated using the execution context. This leads to this check to fail and the objects will not be loaded. Additionally the preferneces objet read is not the one the MessageTool wanted to load but one from another wiki.
Note: this check has been put in loadXWikiDoc, because mysql with certain configuration is doing case insensitive comparison in queries leading to potentially associating objects with the wrong document (suppose you have Test.MyDoc and Test.mydoc which both have Tag Objects. Then you could have the tags of both documents loading with both documents). Now this check could not include the wiki name in it but this would partially fix the issue as the document would still be a document from the wrong wiki or other problems might occur.
// It seems to search before is case insensitive. And this would break the loading if we get an
// object which doesn't really belong to this document
if (!object.getDocumentReference().equals(doc.getDocumentReference())) {
continue ;
}
The core issue here is that the XWiki context object stored in MessageTool and the one in the ExecutionContext are different and this should not happen. When reading documents this can lead to very bad things.
It could be usefull to check that the context parameter in loadXWikiDoc is the same as the context object in the Execution context and issue a serious warning when this happens.

Modified XWikiMessageTool to use XWikiContext provider dynamically instead of keeping a XWikiContext instance forever. Lets see how it goes. Note that the same issue can probably still be found in other places, need a better protection at lower level.

Thomas Mortagne
added a comment - 04/Mar/13 11:14 - edited Modified XWikiMessageTool to use XWikiContext provider dynamically instead of keeping a XWikiContext instance forever. Lets see how it goes. Note that the same issue can probably still be found in other places, need a better protection at lower level.