WebService proxy bug?: "Web Service method name is not valid."

Note: this entry has moved.

Let's suppose you create a proxy for a webservice. Assume that you KNOW that only certain (signed) code should use your proxy, then, it's only natural that you add a StrongNameIdentityPermission to its declaration:

[StrongNameIdentityPermission(SecurityAction.LinkDemand,PublicKey="your_trusted_key")] 
public class MyWebService : SoapHttpClientProtocol 

When you try to call any method with such a proxy, you'll get a pretty strange error:

[your_webmethod] Web Service method name is not valid.

It turns out to be that because of the permission, the internal classes that reflect your type can't get the methods in the class. And when the method to call is looked-up in an internal list and not found, that exception is thrown. However, there's nothing wrong with the method name. The method that throws the exception is SoapHttpClientProtocol.BeforeSerialize, which performs the lookup against a private instance of the type SoapClientType.
The proxy initialization code involves the following steps before that failure:

  1. Proxy constructor initializes a SoapClientType: it passes the current proxy type so that it is reflected to get the web methods.
  2. SoapClientType constructor calls LogicalMethodInfo.Create passing all the proxy public methods, retrieved using Type.GetMethods(BindingFlags.Instance | BindingFlags.Public). This method checks for asynchronous methods and does some other trivial work.
  3. SoapClientType constructor calls SoapReflector.ReflectMethod with each LogicalMethodInfo constructed in the previous step.
I've tracked down the bug by making extensive use of reflection. I've executed each of the methods above, which I identified as the hot-spots for the bug, using trusted code (the assembly signed with the key required by the proxy) and then with untrusted code, which will always be the case for System.Web.Services.dll. Here are the results:
  • SoapClientType constructor: it works as expected, but its internal list of methods available is empty when the constructor is run without the required public key:
    Type t = ReflexHelper.LoadPrivateType(
        "System.Web.Services.Protocols.SoapClientType, System.Web.Services");
    object sct = Activator.CreateInstance(
          t, BindingFlags.Instance | BindingFlags.NonPublic, null, 
            new object[] { typeof(Messaging.MessagingWebService) }, null, null);
    FieldInfo fi = t.GetField("methods", 
          BindingFlags.Instance | BindingFlags.NonPublic);
    Hashtable methods = fi.GetValue(sct) as Hashtable;
    Note that I access a private field called methods which contains the list loaded by the constructor code.
    I went deeper as the methods hastable I got was always empty when I run the unsigned version. The following method is called inside this constructor, and is the next hot-spot.
  • LogicalMethodInfo.Create: it correctly returns the list of methods irrespective of the signature of the calling code. Therefore, the bug isn't here:
    MethodInfo[] mis = typeof(MyWebService).GetMethods(
            BindingFlags.Instance | BindingFlags.Public);
    LogicalMethodInfo[] li = LogicalMethodInfo.Create(mis, LogicalMethodTypes.Sync);
    So I went further to the call on SoapReflector.
  • SoapReflector.ReflectMethod: I couldn't go any further than this because the dependecies are too deep with other private and internal types. However, this method IS the one to blame for the strange error. It fails to reflect the methods on the type because of the StrongNameIdentityPermission, but does not throw any exceptions, thus swallowing the fact that it's a security permission that is failing, instead of the unrelated exception thrown at the moment we actually perform the WebMethod call on the proxy.
    Here's the code that proves it:
    ArrayList list = new ArrayList();
    t = ReflexHelper.LoadPrivateType(
            "System.Web.Services.Protocols.SoapReflector, System.Web.Services");
    MethodInfo reflect = t.GetMethod("ReflectMethod", 
            BindingFlags.Static | BindingFlags.NonPublic);
    
    foreach (LogicalMethodInfo i in li)
    {
        //This always returns null if we're not signed with the key required 
        //(always the case for System.Web.Services.dll)
        object m = reflect.Invoke(
            null, BindingFlags.Static | BindingFlags.NonPublic, null, 
            new object[] { 
                i, true, new XmlReflectionImporter(), 
                new SoapReflectionImporter(), "default-namespace" }, 
                null);
        if (m != null)
            list.Add(m);
    }
    
    Console.WriteLine(list.Count);
    Signing this test console project with the public key required by the proxy results in the correct list of webmethods being loaded into the list object. The object returned by the ReflectMethod is checked in the SoapClientType constructor, but only for non-null returns. If the returned object is null, nothing is done and the internal list of available methods will silently remain empty.
So, how do you protect the class now? Well, put your demand in the class constructor instead of the definition, and nobody will be able to instantiate your proxy unless it's signed with the trusted key. What's more, it makes the code more performant because it avoids the check for each member used (as is the case for the class-level demand).

No Comments