package binding import ( "bytes" "runtime" "testing" "time" ) // TestXMLBindingAdversarialInputs verifies that the XML binding maintains // security boundaries under adversarial inputs including XML bomb / billion // laughs attacks and other malicious XML payloads. // // Security invariant: parsing adversarial XML must not cause unbounded memory // growth or hang the process. The binding must either complete within a // reasonable time/memory budget or return an error — it must never silently // consume excessive resources. func TestXMLBindingAdversarialInputs(t *testing.T) { payloads := []struct { name string payload string }{ { name: "billion_laughs_classic", payload: ` ]> &lol9;`, }, { name: "billion_laughs_shallow", payload: ` ]> &d;`, }, { name: "quadratic_blowup", payload: ` ]> &x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;`, }, { name: "deeply_nested_elements", payload: func() string { var buf bytes.Buffer buf.WriteString(``) depth := 100000 for i := 0; i < depth; i++ { buf.WriteString("") } buf.WriteString("deep") for i := 0; i < depth; i++ { buf.WriteString("") } return buf.String() }(), }, { name: "large_attribute_value", payload: func() string { var buf bytes.Buffer buf.WriteString(`value`) return buf.String() }(), }, { name: "many_attributes", payload: func() string { var buf bytes.Buffer buf.WriteString(`content`) return buf.String() }(), }, { name: "entity_in_attribute", payload: ` ]> value`, }, { name: "malformed_xml", payload: `text`, }, { name: "null_bytes", payload: "\x00\x00\x00", }, { name: "unicode_bomb", payload: ` ]> &u3;`, }, } // Memory limit: 256 MB growth allowed per parse attempt const maxMemoryGrowthBytes = 256 * 1024 * 1024 // Time limit per parse attempt const maxDuration = 5 * time.Second type Target struct { Value string `xml:",chardata"` Attr string `xml:",attr"` } xmlBind := xmlBinding{} for _, tc := range payloads { t.Run(tc.name, func(t *testing.T) { // Measure baseline memory var memBefore runtime.MemStats runtime.GC() runtime.ReadMemStats(&memBefore) done := make(chan error, 1) start := time.Now() go func() { var obj Target err := xmlBind.BindBody([]byte(tc.payload), &obj) done <- err }() select { case err := <-done: elapsed := time.Since(start) // Measure memory after var memAfter runtime.MemStats runtime.GC() runtime.ReadMemStats(&memAfter) // Security invariant 1: must complete within time limit if elapsed > maxDuration { t.Errorf("SECURITY VIOLATION: XML parsing took %v (limit %v) for payload %q — possible DoS", elapsed, maxDuration, tc.name) } // Security invariant 2: memory growth must be bounded if memAfter.TotalAlloc > memBefore.TotalAlloc { growth := memAfter.TotalAlloc - memBefore.TotalAlloc if growth > maxMemoryGrowthBytes { t.Errorf("SECURITY VIOLATION: XML parsing allocated %d bytes (limit %d) for payload %q — possible memory bomb", growth, maxMemoryGrowthBytes, tc.name) } } // Security invariant 3: if parsing succeeded, the result must not // be astronomically large (entity expansion must be bounded) if err == nil { // A successful parse of a bomb payload is a security concern // if the result is huge; log it as a warning t.Logf("payload %q parsed without error in %v (err=%v)", tc.name, elapsed, err) } case <-time.After(maxDuration): t.Errorf("SECURITY VIOLATION: XML parsing timed out after %v for payload %q — DoS vulnerability confirmed", maxDuration, tc.name) } }) } }