VYPR
High severityNVD Advisory· Published Feb 26, 2026· Updated Apr 15, 2026

CVE-2026-27830

CVE-2026-27830

Description

c3p0, a JDBC Connection pooling library, is vulnerable to attack via maliciously crafted Java-serialized objects and javax.naming.Reference instances. Several c3p0 ConnectionPoolDataSource implementations have a property called userOverridesAsString which conceptually represents a Map<String,Map<String,String>>. Prior to v0.12.0, that property was maintained as a hex-encoded serialized object. Any attacker able to reset this property, on an existing ConnectionPoolDataSource or via maliciously crafted serialized objects or javax.naming.Reference instances could be tailored execute unexpected code on the application's CLASSPATH. The danger of this vulnerability was strongly magnified by vulnerabilities in c3p0's main dependency, mchange-commons-java. This library includes code that mirrors early implementations of JNDI functionality, including ungated support for remote factoryClassLocation values. Attackers could set c3p0's userOverridesAsString hex-encoded serialized objects that include objects "indirectly serialized" via JNDI references. Deserialization of those objects and dereferencing of the embedded javax.naming.Reference objects could provoke download and execution of malicious code from a remote factoryClassLocation. Although hazard presented by c3p0's vulnerabilites are exarcerbated by vulnerabilities in mchange-commons-java, use of Java-serialized-object hex as the format for a writable Java-Bean property, of objects that may be exposed across JNDI interfaces, represents a serious independent fragility. The userOverridesAsString property of c3p0 ConnectionPoolDataSource classes has been reimplemented to use a safe CSV-based format, rather than rely upon potentially dangerous Java object deserialization. c3p0-0.12.0+ and above depend upon mchange-commons-java 0.4.0+, which gates support for remote factoryClassLocation values by configuration parameters that default to restrictive values. c3p0 additionally enforces the new mchange-commons-java com.mchange.v2.naming.nameGuardClassName to prevent injection of unexpected, potentially remote JNDI names. There is no supported workaround for versions of c3p0 prior to 0.12.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
com.mchange:c3p0Maven
< 0.12.00.12.0

Affected products

1

Patches

1
e14cbd8166e4

Reimplement userOverridesAsString without relying on unnecessarily dangerous Java Serialization.

https://github.com/swaldman/c3p0Steve WaldmanFeb 16, 2026via ghsa
3 files changed · +111 4
  • src/com/mchange/v2/c3p0/cfg/C3P0Config.java+2 1 modified
    @@ -11,6 +11,7 @@
     import java.lang.reflect.Method;
     import com.mchange.v1.lang.BooleanUtils;
     import com.mchange.v2.c3p0.C3P0Registry;
    +import com.mchange.v2.csv.MalformedCsvException;
     
     //all internal maps should be HashMaps (the implementation presumes HashMaps)
     
    @@ -439,7 +440,7 @@ public static Map getUserOverrides( String configName )
     	return (out.isEmpty() ? null : out );
         }
     
    -    public static String getUserOverridesAsString(String configName) throws IOException
    +    public static String getUserOverridesAsString(String configName) throws IOException, MalformedCsvException
         {
     	Map userOverrides = getUserOverrides( configName );
     	if (userOverrides == null)
    
  • src/com/mchange/v2/c3p0/impl/C3P0ImplUtils.java+93 1 modified
    @@ -1,5 +1,6 @@
     package com.mchange.v2.c3p0.impl;
     
    +import java.io.*;
     import java.beans.*;
     import java.util.*;
     import java.lang.reflect.*;
    @@ -10,7 +11,8 @@
     import com.mchange.v2.c3p0.*;
     import com.mchange.v2.c3p0.cfg.*;
     
    -import java.io.IOException;
    +import com.mchange.v2.csv.*;
    +
     import java.sql.Connection;
     import java.sql.SQLException;
     import com.mchange.lang.ByteUtils;
    @@ -231,6 +233,12 @@ public static boolean supportsMethod(Object target, String mname, Class[] argTyp
     	    }
         }
     
    +    /*
    +    // Java Serialization-based userOverridesAsString format creates an unnecessary attack surface for
    +    // placing malicious objects in the serialized format and provoking deserialization.
    +    //
    +    // We'll transition to a simpler, less dangerous format. 
    +
         private final static String HASM_HEADER = "HexAsciiSerializedMap";
     
         public static String createUserOverridesAsString( Map userOverrides ) throws IOException
    @@ -254,6 +262,90 @@ public static Map parseUserOverridesAsString( String userOverridesAsString ) thr
     	else
     	    return Collections.EMPTY_MAP;
         }
    +    */
    +
    +    // we serialize user overrides to "ragged CSV".
    +    // CSV lines containing only a single element are interpreted as the user for whom we are overriding config
    +    // lines following containing two elements are the config param overrides for that user.
    +    public static String createUserOverridesAsString( Map userOverrides ) throws IOException, MalformedCsvException
    +    {
    +        Writer w = new StringWriter();
    +        for (Object o : userOverrides.keySet())
    +        {
    +            // we don't check the type. We're treating this as basically an assertion, in our old-school, loosely typed Java
    +            // we'll let the user see a ClassCastException if something has been messed with
    +            String user = (String) o;
    +            w.append(FastCsvUtils.generateQuotedCsvItem(user));
    +            w.append("\r\n");
    +            Map userProps = (Map) userOverrides.get(user);
    +            String[] propNamePropValAsString = new String[2];
    +            for (Object pn : userProps.keySet())
    +            {
    +                propNamePropValAsString[0] = (String) pn;
    +                propNamePropValAsString[1] = (String) userProps.get(pn);
    +                w.append(FastCsvUtils.generateCsvLineQuotedUnterminated(propNamePropValAsString));
    +                w.append("\r\n");
    +            }
    +        }
    +        return w.toString();
    +    }
    +
    +    private static Map parseSingleUserMap(String userOverridesAsString, BufferedReader br, String[] nextUserHolder) throws IOException, MalformedCsvException
    +    {
    +        Map out = new HashMap();
    +        nextUserHolder[0] = null;
    +        String line = FastCsvUtils.csvReadLine(br);
    +        if (line == null)
    +        {
    +            nextUserHolder[0] = null;
    +            return Collections.EMPTY_MAP;
    +        }
    +        else
    +        {
    +            do
    +            {
    +                String[] items = FastCsvUtils.csvSplitLine( line );
    +                switch ( items.length )
    +                {
    +                case 2: // this is an expected property override line
    +                    out.put(items[0],items[1]);
    +                    break;
    +                case 1: // this is the next user name
    +                    nextUserHolder[0] = items[0];
    +                    break;
    +                default:
    +                    throw new IOException("Unexpected CSV line in userOverridesAsString ('" + line + "'). All line should have 1 or 2 items:\r\n" + userOverridesAsString);
    +                }
    +            }
    +            while (nextUserHolder[0] == null && (line = FastCsvUtils.csvReadLine(br)) != null); // either EOL or discovery of next user terminates
    +            return Collections.unmodifiableMap(out);
    +        }
    +    }
    +
    +    public static Map parseUserOverridesAsString( String userOverridesAsString ) throws IOException, MalformedCsvException
    +    {
    +        String[] nextUserHolder = new String[1];
    +        BufferedReader br = new BufferedReader(new StringReader(userOverridesAsString));
    +        String line = FastCsvUtils.csvReadLine(br);
    +        if ( line == null )
    +            return Collections.EMPTY_MAP;
    +        else
    +        {
    +            Map out = new HashMap();
    +            String[] items = FastCsvUtils.csvSplitLine(line);
    +            if (items.length != 1)
    +                throw new IOException("Cannot parse userOverridesAsString, one element line naming the user should come before other data:\r\n" + userOverridesAsString);
    +            String username = items[0];
    +            do
    +            {
    +                Map overrides = parseSingleUserMap(userOverridesAsString, br, nextUserHolder);
    +                out.put(username, overrides);
    +                username = nextUserHolder[0];
    +            }
    +            while (username != null);
    +            return Collections.unmodifiableMap(out);
    +        }
    +    }
     
         public static void runWithContextClassLoaderAndPrivileges( final String contextClassLoaderSource, final boolean privilege_spawned_threads, final Runnable runnable )
         {
    
  • test/resources-local/c3p0.properties+16 2 modified
    @@ -54,7 +54,7 @@ c3p0.testConnectionOnCheckout=true
     #com.mchange.v2.c3p0.cfg.xml=classloader:META-INF/poop.xml
     #com.mchange.v2.c3p0.cfg.xml=/data/home/swaldman/development/gitproj/c3p0/src/test-properties/META-INF/poop.xml
     
    -com.mchange.v2.log.MLog=com.mchange.v2.log.jdk14logging.Jdk14MLog
    +#com.mchange.v2.log.MLog=com.mchange.v2.log.jdk14logging.Jdk14MLog
     #com.mchange.v2.log.MLog=com.mchange.v2.log.FallbackMLog
     #com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL=FINER
     #com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL=ALL
    @@ -71,7 +71,7 @@ com.mchange.v2.log.MLog=com.mchange.v2.log.jdk14logging.Jdk14MLog
     
     #com.mchange.v2.resourcepool.experimental.useScatteredAcquireTask=false
     
    -# make sure we warn on unknown config
    +#make sure we warn on unknown config
     #c3p0.poop=stinky
     
     #prop-style named config
    @@ -88,3 +88,17 @@ com.mchange.v2.log.MLog=com.mchange.v2.log.jdk14logging.Jdk14MLog
     #com.mchange.v2.c3p0.impl.DefaultConnectionTester.querylessTestRunner=IS_VALID
     #com.mchange.v2.c3p0.impl.DefaultConnectionTester.querylessTestRunner=SWITCH
     #com.mchange.v2.c3p0.impl.DefaultConnectionTester.querylessTestRunner=THREAD_LOCAL
    +
    +#define params for a user called 'steve'
    +#c3p0.user-overrides.steve.maxPoolSize=15
    +#c3p0.user-overrides.steve.minPoolSize=5
    +
    +#define params for a user called 'ramona'
    +#c3p0.user-overrides.ramona.maxPoolSize=50
    +#c3p0.user-overrides.ramona.minPoolSize=20
    +
    +#c3p0.user-overrides.swaldman.preferredTestQuery=SXLECT 1
    +
    +
    +
    +
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.