serve dlna: bound SOAP request bodies
The unauthenticated DLNA control endpoint decoded arbitrary SOAP bodies into an in-memory XML field. A LAN client could send a large request and exhaust the server's memory. Limit SOAP request bodies to 1 MiB and return 413 when the limit is exceeded.
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -148,6 +149,7 @@ const (
|
|||||||
rootDescPath = "/rootDesc.xml"
|
rootDescPath = "/rootDesc.xml"
|
||||||
resPath = "/r/"
|
resPath = "/r/"
|
||||||
serviceControlURL = "/ctl"
|
serviceControlURL = "/ctl"
|
||||||
|
maxSOAPBodySize = 1 << 20
|
||||||
)
|
)
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
@@ -303,7 +305,13 @@ func (s *server) serviceControlHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var env soap.Envelope
|
var env soap.Envelope
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, maxSOAPBodySize)
|
||||||
if err := xml.NewDecoder(r.Body).Decode(&env); err != nil {
|
if err := xml.NewDecoder(r.Body).Decode(&env); err != nil {
|
||||||
|
var maxBytesErr *http.MaxBytesError
|
||||||
|
if errors.As(err, &maxBytesErr) {
|
||||||
|
http.Error(w, "SOAP request body too large", http.StatusRequestEntityTooLarge)
|
||||||
|
return
|
||||||
|
}
|
||||||
serveError(ctx, s, w, "Could not parse SOAP request body", err)
|
serveError(ctx, s, w, "Could not parse SOAP request body", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,22 @@ func TestInit(t *testing.T) {
|
|||||||
startServer(t, f)
|
startServer(t, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServiceControlRejectsOversizedBody(t *testing.T) {
|
||||||
|
body := `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body>` +
|
||||||
|
`<u:RegisterDevice xmlns:u="urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1">` +
|
||||||
|
strings.Repeat("x", maxSOAPBodySize) +
|
||||||
|
`</u:RegisterDevice></s:Body></s:Envelope>`
|
||||||
|
req, err := http.NewRequest("POST", baseURL+serviceControlURL, strings.NewReader(body))
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("SOAPACTION", `"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1#RegisterDevice"`)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
require.NoError(t, resp.Body.Close())
|
||||||
|
}()
|
||||||
|
assert.Equal(t, http.StatusRequestEntityTooLarge, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure that it serves rootDesc.xml (SCPD in uPnP parlance).
|
// Make sure that it serves rootDesc.xml (SCPD in uPnP parlance).
|
||||||
func TestRootSCPD(t *testing.T) {
|
func TestRootSCPD(t *testing.T) {
|
||||||
req, err := http.NewRequest("GET", baseURL+rootDescPath, nil)
|
req, err := http.NewRequest("GET", baseURL+rootDescPath, nil)
|
||||||
|
|||||||
Reference in New Issue
Block a user