diff --git a/html5.go b/html5.go index 72c38c4..22bdab0 100644 --- a/html5.go +++ b/html5.go @@ -204,6 +204,7 @@ func (h *h5State) stateMarkupDeclarationOpen() bool { } func (h *h5State) stateSelfClosingStartTag() bool { + // WARNING: This function is partially inlined into stateBeforeAttributeName() if h.pos >= h.len { return false } @@ -495,26 +496,35 @@ func (h *h5State) stateAttributeName() bool { } func (h *h5State) stateBeforeAttributeName() bool { - ch := h.skipWhite() - switch ch { - case byteEOF: - return false + for h.pos < h.len { + ch := h.skipWhite() + switch ch { + case byteEOF: + return false - case byteSlash: - h.pos++ - return h.stateSelfClosingStartTag() + case byteSlash: + h.pos++ + // Logically, we want to call stateSelfClosingStartTag() here + // But this function might call us back and result in deep recursion, so + // we iterate within this function instead. + if h.pos < h.len && h.s[h.pos] != byteGT { + continue + } + return h.stateSelfClosingStartTag() - case byteGT: - h.state = h.stateData - h.tokenStart = h.s[h.pos:] - h.tokenLen = 1 - h.tokenType = html5TypeTagNameClose - h.pos++ - return true + case byteGT: + h.state = h.stateData + h.tokenStart = h.s[h.pos:] + h.tokenLen = 1 + h.tokenType = html5TypeTagNameClose + h.pos++ + return true - default: - return h.stateAttributeName() + default: + return h.stateAttributeName() + } } + return false } // 12.2.4.41 diff --git a/xss_stack_overflow_test.go b/xss_stack_overflow_test.go new file mode 100644 index 0000000..f22a51c --- /dev/null +++ b/xss_stack_overflow_test.go @@ -0,0 +1,16 @@ +package libinjection + +import ( + "testing" +) + +func TestMemory(t *testing.T) { + size := 10_000_000 + input := make([]byte, size) + for i := range input { + input[i] = '/' + } + + // should not overflow the stack + IsXSS(string(input)) +}