Skip to content

Commit df7da6c

Browse files
committed
Fix BZ 65736 replace forceString with a String setter lookup
1 parent 3448011 commit df7da6c

File tree

6 files changed

+151
-153
lines changed

6 files changed

+151
-153
lines changed

java/org/apache/naming/factory/BeanFactory.java

Lines changed: 21 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,9 @@
1919
import java.beans.BeanInfo;
2020
import java.beans.Introspector;
2121
import java.beans.PropertyDescriptor;
22-
import java.lang.reflect.InvocationTargetException;
2322
import java.lang.reflect.Method;
2423
import java.util.Enumeration;
25-
import java.util.HashMap;
2624
import java.util.Hashtable;
27-
import java.util.Locale;
28-
import java.util.Map;
2925

3026
import javax.naming.Context;
3127
import javax.naming.Name;
@@ -34,6 +30,8 @@
3430
import javax.naming.Reference;
3531
import javax.naming.spi.ObjectFactory;
3632

33+
import org.apache.juli.logging.Log;
34+
import org.apache.juli.logging.LogFactory;
3735
import org.apache.naming.ResourceRef;
3836
import org.apache.naming.StringManager;
3937

@@ -92,6 +90,8 @@ public class BeanFactory implements ObjectFactory {
9290

9391
private static final StringManager sm = StringManager.getManager(BeanFactory.class);
9492

93+
private final Log log = LogFactory.getLog(BeanFactory.class); // Not static
94+
9595
/**
9696
* Create a new Bean instance.
9797
*
@@ -125,44 +125,14 @@ public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtabl
125125

126126
Object bean = beanClass.getConstructor().newInstance();
127127

128-
/* Look for properties with explicitly configured setter */
128+
// Look for the removed forceString option
129129
RefAddr ra = ref.get("forceString");
130-
Map<String, Method> forced = new HashMap<>();
131-
String value;
132-
133130
if (ra != null) {
134-
value = (String)ra.getContent();
135-
Class<?> paramTypes[] = new Class[1];
136-
paramTypes[0] = String.class;
137-
String setterName;
138-
int index;
139-
140-
/* Items are given as comma separated list */
141-
for (String param: value.split(",")) {
142-
param = param.trim();
143-
/* A single item can either be of the form name=method
144-
* or just a property name (and we will use a standard
145-
* setter) */
146-
index = param.indexOf('=');
147-
if (index >= 0) {
148-
setterName = param.substring(index + 1).trim();
149-
param = param.substring(0, index).trim();
150-
} else {
151-
setterName = "set" +
152-
param.substring(0, 1).toUpperCase(Locale.ENGLISH) +
153-
param.substring(1);
154-
}
155-
try {
156-
forced.put(param, beanClass.getMethod(setterName, paramTypes));
157-
} catch (NoSuchMethodException|SecurityException ex) {
158-
throw new NamingException
159-
("Forced String setter " + setterName +
160-
" not found for property " + param);
161-
}
162-
}
131+
log.warn(sm.getString("beanFactory.noForceString"));
163132
}
164133

165134
Enumeration<RefAddr> e = ref.getAll();
135+
String value;
166136

167137
while (e.hasMoreElements()) {
168138

@@ -180,28 +150,13 @@ public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtabl
180150

181151
Object[] valueArray = new Object[1];
182152

183-
/* Shortcut for properties with explicitly configured setter */
184-
Method method = forced.get(propName);
185-
if (method != null) {
186-
valueArray[0] = value;
187-
try {
188-
method.invoke(bean, valueArray);
189-
} catch (IllegalAccessException|
190-
IllegalArgumentException|
191-
InvocationTargetException ex) {
192-
throw new NamingException
193-
("Forced String setter " + method.getName() +
194-
" threw exception for property " + propName);
195-
}
196-
continue;
197-
}
198-
199153
int i = 0;
200154
for (i = 0; i < pda.length; i++) {
201155

202156
if (pda[i].getName().equals(propName)) {
203157

204158
Class<?> propType = pda[i].getPropertyType();
159+
Method setProp = pda[i].getWriteMethod();
205160

206161
if (propType.equals(String.class)) {
207162
valueArray[0] = value;
@@ -221,12 +176,22 @@ public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtabl
221176
valueArray[0] = Double.valueOf(value);
222177
} else if (propType.equals(Boolean.class) || propType.equals(boolean.class)) {
223178
valueArray[0] = Boolean.valueOf(value);
179+
} else if (setProp != null) {
180+
// This is a Tomcat specific extension and is not part of the
181+
// Java Bean specification.
182+
String setterName = setProp.getName();
183+
try {
184+
setProp = bean.getClass().getMethod(setterName, String.class);
185+
valueArray[0] = value;
186+
} catch (NoSuchMethodException nsme) {
187+
throw new NamingException(sm.getString(
188+
"beanFactory.noStringConversion", propName, propType.getName()));
189+
}
224190
} else {
225-
throw new NamingException(
226-
sm.getString("beanFactory.noStringConversion", propName, propType.getName()));
191+
throw new NamingException(sm.getString(
192+
"beanFactory.noStringConversion", propName, propType.getName()));
227193
}
228194

229-
Method setProp = pda[i].getWriteMethod();
230195
if (setProp != null) {
231196
setProp.invoke(bean, valueArray);
232197
} else {

java/org/apache/naming/factory/LocalStrings.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# limitations under the License.
1515

1616
beanFactory.classNotFound=Class not found: [{0}]
17+
beanFactory.noForceString=The forceString option has been removed as a security hardening measure. Instead, if the setter method doesn't use String, a primitive or a primitive wrapper, the factory will look for a method with the same name as the setter that accepts a String and use that if found.
1718
beanFactory.noSetMethod=No set method found for property [{0}]
1819
beanFactory.noStringConversion=String conversion for property [{0}] of type [{1}] not available
1920
beanFactory.readOnlyProperty=Write not allowed for property [{0}]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.naming.factory;
18+
19+
import javax.naming.StringRefAddr;
20+
21+
import org.junit.Assert;
22+
import org.junit.Test;
23+
24+
import org.apache.naming.ResourceRef;
25+
26+
public class TestBeanFactory {
27+
28+
private static final String IP_ADDRESS = "127.0.0.1";
29+
30+
@Test
31+
public void testForceStringAlternativeWithout() throws Exception {
32+
doTestForceStringAlternatove(false);
33+
}
34+
35+
36+
@Test
37+
public void testForceStringAlternativeWith() throws Exception {
38+
doTestForceStringAlternatove(true);
39+
}
40+
41+
42+
private void doTestForceStringAlternatove(boolean useForceString) throws Exception {
43+
44+
// Create the resource definition
45+
ResourceRef resourceRef = new ResourceRef(TesterBean.class.getName(), null, null, null, false);
46+
StringRefAddr server = new StringRefAddr("server", IP_ADDRESS);
47+
resourceRef.add(server);
48+
if (useForceString) {
49+
StringRefAddr force = new StringRefAddr("forceString", "server");
50+
resourceRef.add(force);
51+
}
52+
53+
// Create the factory
54+
BeanFactory factory = new BeanFactory();
55+
56+
// Use the factory to create the resource from the definition
57+
Object obj = factory.getObjectInstance(resourceRef, null, null, null);
58+
59+
// Check the correct type was created
60+
Assert.assertNotNull(obj);
61+
Assert.assertEquals(obj.getClass(), TesterBean.class);
62+
// Check the server field was set
63+
TesterBean result = (TesterBean) obj;
64+
Assert.assertNotNull(result.getServer());
65+
Assert.assertEquals(IP_ADDRESS, result.getServer().getHostAddress());
66+
}
67+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.naming.factory;
18+
19+
import java.net.InetAddress;
20+
import java.net.UnknownHostException;
21+
22+
public class TesterBean {
23+
24+
private InetAddress server;
25+
26+
public InetAddress getServer() {
27+
return server;
28+
}
29+
30+
public void setServer(InetAddress server) {
31+
this.server = server;
32+
}
33+
34+
public void setServer(String server) {
35+
try {
36+
this.server = InetAddress.getByName(server);
37+
} catch (UnknownHostException e) {
38+
throw new IllegalArgumentException(e);
39+
}
40+
}
41+
}

webapps/docs/changelog.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@
107107
<section name="Tomcat 9.0.63 (remm)" rtext="in development">
108108
<subsection name="Catalina">
109109
<changelog>
110+
<fix>
111+
<bug>65736</bug>: Disable the <code>forceString</code> option for the
112+
JNDI <code>BeanFactory</code> and replace it with an automatic search
113+
for an alternative setter with the same name that accepts a
114+
<code>String</code>. This is a security hardening measure. (markt)
115+
</fix>
110116
<scode>
111117
<bug>65853</bug>: Refactor the <code>CsrfPreventionFilter</code> to make
112118
it easier for sub-classes to modify the nonce generation and storage.

webapps/docs/jndi-resources-howto.xml

Lines changed: 15 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -328,103 +328,21 @@ writer.println("foo = " + bean.getFoo() + ", bar = " +
328328
<code>foo</code> property (although we could have), the bean will
329329
contain whatever default value is set up by its constructor.</p>
330330

331-
<p>Some beans have properties with types that cannot automatically be
332-
converted from a string value. Setting such properties using the Tomcat
333-
BeanFactory will fail with a NamingException. In cases were those beans
334-
provide methods to set the properties from a string value, the Tomcat
335-
BeanFactory can be configured to use these methods. The configuration is
336-
done with the <code>forceString</code> attribute.</p>
337-
338-
<p>Assume our bean looks like this:</p>
339-
340-
<source><![CDATA[package com.mycompany;
341-
342-
import java.net.InetAddress;
343-
import java.net.UnknownHostException;
344-
345-
public class MyBean2 {
346-
347-
private InetAddress local = null;
348-
349-
public InetAddress getLocal() {
350-
return local;
351-
}
352-
353-
public void setLocal(InetAddress ip) {
354-
local = ip;
355-
}
356-
357-
public void setLocal(String localHost) {
358-
try {
359-
local = InetAddress.getByName(localHost);
360-
} catch (UnknownHostException ex) {
361-
}
362-
}
363-
364-
private InetAddress remote = null;
365-
366-
public InetAddress getRemote() {
367-
return remote;
368-
}
369-
370-
public void setRemote(InetAddress ip) {
371-
remote = ip;
372-
}
373-
374-
public void host(String remoteHost) {
375-
try {
376-
remote = InetAddress.getByName(remoteHost);
377-
} catch (UnknownHostException ex) {
378-
}
379-
}
380-
381-
}]]></source>
382-
383-
<p>The bean has two properties, both are of type <code>InetAddress</code>.
384-
The first property <code>local</code> has an additional setter taking a
385-
string argument. By default the Tomcat BeanFactory would try to use the
386-
automatically detected setter with the same argument type as the property
387-
type and then throw a NamingException, because it is not prepared to convert
388-
the given string attribute value to <code>InetAddress</code>.
389-
We can tell the Tomcat BeanFactory to use the other setter like that:</p>
390-
391-
<source><![CDATA[<Context ...>
392-
...
393-
<Resource name="bean/MyBeanFactory" auth="Container"
394-
type="com.mycompany.MyBean2"
395-
factory="org.apache.naming.factory.BeanFactory"
396-
forceString="local"
397-
local="localhost"/>
398-
...
399-
</Context>]]></source>
400-
401-
<p>The bean property <code>remote</code> can also be set from a string,
402-
but one has to use the non-standard method name <code>host</code>.
403-
To set <code>local</code> and <code>remote</code> use the following
404-
configuration:</p>
405-
406-
<source><![CDATA[<Context ...>
407-
...
408-
<Resource name="bean/MyBeanFactory" auth="Container"
409-
type="com.mycompany.MyBean2"
410-
factory="org.apache.naming.factory.BeanFactory"
411-
forceString="local,remote=host"
412-
local="localhost"
413-
remote="tomcat.apache.org"/>
414-
...
415-
</Context>]]></source>
416-
417-
<p>Multiple property descriptions can be combined in
418-
<code>forceString</code> by concatenation with comma as a separator.
419-
Each property description consists of either only the property name
420-
in which case the BeanFactory calls the setter method. Or it consist
421-
of <code>name=method</code> in which case the property named
422-
<code>name</code> is set by calling method <code>method</code>.
423-
For properties of types <code>String</code> or of primitive type
424-
or of their associated primitive wrapper classes using
425-
<code>forceString</code> is not needed. The correct setter will be
426-
automatically detected and argument conversion will be applied.</p>
427-
331+
<p>If the bean property is of type <code>String</code>, the BeanFactory
332+
will call the property setter using the provided property value. If the
333+
bean property type is a primitive or a primitive wrapper, the the
334+
BeanFactory will convert the value to the appropriate primitive or
335+
primitive wrapper and then use that value when calling the setter. Some
336+
beans have properties with types that cannot automatically be converted
337+
from <code>String</code>. If the bean provides an alternative setter with
338+
the same name that does take a <code>String</code>, the BeanFactory will
339+
attempt to use that setter. If the BeanFactory cannot use the value or
340+
perform an appropriate conversion, setting the property will fail with a
341+
NamingException.</p>
342+
343+
<p>The <code>forceString</code> property available in earlier Tomcat
344+
releases has been removed as a security hardening measure.</p>
345+
428346
</subsection>
429347

430348

0 commit comments

Comments
 (0)