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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions