Skip to content

NullReferenceException in ParameterCompatibleFilter.ParseResource() during resource validation #5114

@brendankowitz

Description

@brendankowitz

🐛 Bug Report

Description

Resource validation operations are failing with NullReferenceException in the ParameterCompatibleFilter.ParseResource() method when processing Parameters resources that don't contain the expected "resource" parameter.

Error Details

System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Health.Fhir.Api.Features.Filters.ParameterCompatibleFilter.ParseResource(Resource resource)
   in /_/src/Microsoft.Health.Fhir.Shared.Api/Features/Filters/ParameterCompatibleFilter.cs:line 26
   at Microsoft.Health.Fhir.Api.Features.Filters.ValidateResourceTypeFilterAttribute.OnActionExecuting(ActionExecutingContext context)
   in /_/src/Microsoft.Health.Fhir.Shared.Api/Features/Filters/ValidateResourceTypeFilterAttribute.cs:line 33

Root Cause Analysis

The issue occurs in ParameterCompatibleFilter.cs line 26:

protected Resource ParseResource(Resource resource)
{
    if (_allowParametersResource && resource.TypeName == KnownResourceTypes.Parameters)
    {
        resource = ((Parameters)resource).Parameter.Find(param => param.Name.Equals("resource", StringComparison.OrdinalIgnoreCase)).Resource;  // LINE 26 - NULL REFERENCE
    }

    return resource;
}

The issue occurs when:

  1. resource.TypeName == KnownResourceTypes.Parameters is true
  2. ((Parameters)resource).Parameter.Find(...) returns null because:
    • No parameter with name "resource" exists
    • The Parameter collection is null
    • The found parameter has a null Resource property
  3. Accessing .Resource on a null parameter causes the NullReferenceException

Reproduction Steps

  1. Create a Parameters resource with either:
    • No parameter named "resource"
    • A parameter named "resource" with a null Resource property
    • A null Parameter collection
  2. Send this Parameters resource through the validation pipeline
  3. The NullReferenceException will be thrown at line 26

Minimal Reproduction Example

var parameters = new Parameters();
parameters.Parameter = new List<Parameters.ParameterComponent>
{
    new Parameters.ParameterComponent
    {
        Name = "resource",
        Resource = null  // This causes the NullReferenceException
    }
};

// This will throw NullReferenceException when processed by ParameterCompatibleFilter

Proposed Fix

Add defensive null checking in the ParseResource method:

protected Resource ParseResource(Resource resource)
{
    if (_allowParametersResource && resource?.TypeName == KnownResourceTypes.Parameters)
    {
        var parameters = (Parameters)resource;
        var resourceParam = parameters.Parameter?.Find(param => param.Name.Equals("resource", StringComparison.OrdinalIgnoreCase));
        
        if (resourceParam?.Resource != null)
        {
            resource = resourceParam.Resource;
        }
        // If no resource parameter found or it's null, return the original Parameters resource
        // This maintains backward compatibility while preventing the crash
    }

    return resource;
}

Alternative Solution

Add validation in the calling methods to handle null results from ParseResource():

public override void OnActionExecuting(ActionExecutingContext context)
{
    // ... existing code ...
    
    if (context.ActionArguments.TryGetValue(KnownActionParameterNames.Resource, out var parsedModel))
    {
        var resource = ParseResource((Resource)parsedModel);
        if (resource == null)
        {
            throw new ResourceNotValidException("Failed to extract resource from Parameters");
        }
        
        ValidateType(resource, (string)actionModelType);
    }
}

Test Cases to Add

[Fact]
public void ParseResource_WithNullParameterResource_ShouldNotThrow()
{
    var parameters = new Parameters();
    parameters.Parameter = new List<Parameters.ParameterComponent>
    {
        new Parameters.ParameterComponent { Name = "resource", Resource = null }
    };
    
    var filter = new TestParameterCompatibleFilter(allowParametersResource: true);
    var result = filter.ParseResource(parameters);
    
    Assert.NotNull(result); // Should not throw NullReferenceException
}

[Fact]
public void ParseResource_WithMissingResourceParameter_ShouldNotThrow()
{
    var parameters = new Parameters();
    parameters.Parameter = new List<Parameters.ParameterComponent>
    {
        new Parameters.ParameterComponent { Name = "otherParam", Value = new FhirString("test") }
    };
    
    var filter = new TestParameterCompatibleFilter(allowParametersResource: true);
    var result = filter.ParseResource(parameters);
    
    Assert.NotNull(result); // Should not throw NullReferenceException
}

Impact

  • Severity: High - Validation operations fail with HTTP 500 errors
  • Affected Operations: Resource validation endpoints that accept Parameters resources
  • Client Impact: Any client sending Parameters resources without proper "resource" parameter structure

Backward Compatibility

The proposed fix maintains backward compatibility by:

  • Still extracting the inner resource when properly structured Parameters are provided
  • Gracefully handling malformed Parameters by returning the original Parameters resource
  • Not changing the method signature or expected behavior for valid inputs

This is a defensive programming issue where the code assumes Parameters resources always contain a valid "resource" parameter. The fix should prevent crashes while maintaining existing functionality.

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions