Skip to content

fix: neutralize control characters in lcov report fields - #2226

Merged
nedbat merged 1 commit into
coveragepy:mainfrom
rajath201:lcov-field-control-chars
Aug 6, 2026
Merged

nedbat merged 1 commit into
coveragepy:mainfrom
rajath201:lcov-field-control-chars

Conversation

@rajath201

Copy link
Copy Markdown
Contributor

The lcov writer drops file names and the function and branch fields straight into its newline-delimited records with no escaping, unlike the html, markdown, xml, and json reports which each neutralize untrusted values. A source file whose name contains a newline (legal on POSIX, and reachable whenever coverage measures a tree you don't fully control) spills past the SF: line and forges its own SF/DA/LH records, so a downstream reader like genhtml or a CI coverage gate sees fabricated files and inflated hit counts.

lcov_field replaces control characters in each field right before it's written, keeping the value on one line while leaving normal names (printable, including non-ASCII) untouched. Keeping the guard at the write sites covers every field the format treats as free text in one spot, with no change to valid output. The added test measures a file whose name embeds a fake record and checks the report stays a single SF record.

@rajath201

Copy link
Copy Markdown
Contributor Author

gentle ping

@nedbat

nedbat commented Aug 2, 2026

Copy link
Copy Markdown
Member

TBH, this seems like a very unlikely scenario, and isn't a security concern. A file name with a newline will likely cause havoc in many places. A broken .lcov file seems like the least of their worries, and will only cause troubles for themselves.

Unless you have a more compelling argument, I am inclined to close this.

@devdanzin

Copy link
Copy Markdown
Contributor

I independently found this same issue.

The only (pretty weak) argument I can muster is consistency: dd80635 and e06eb34 did something similar.

I think every line-oriented LCOV consumer like genhtml, Coveralls, Codecov, Sonar would trip on the invalid line created, but not sure that counts for much.

@rajath201

Copy link
Copy Markdown
Contributor Author

No argument on the security framing, and newline filenames are certainly rare. The case I'd make is the one devdanzin raised: after dd80635 and e06eb34, lcov is the only writer left that can produce output its own format can't represent, and the failure can be silent since the spilled name reads back as extra SF/LH records with bogus totals rather than a parse error. The guard is a few lines at the write sites and leaves all valid output byte-identical. If that still doesn't clear the bar, no objection to closing it.

@nedbat

nedbat commented Aug 4, 2026

Copy link
Copy Markdown
Member

OK, rebase onto main, and we can get it merged.

@rajath201
rajath201 force-pushed the lcov-field-control-chars branch from 930fb65 to 45eb03d Compare August 6, 2026 11:18
@rajath201

Copy link
Copy Markdown
Contributor Author

Rebased onto main. Only fixup needed was re-placing the changelog entry in the current Unreleased section, since the old one has since shipped. Code and test are unchanged, and the lcov tests pass locally.

@nedbat
nedbat merged commit 53a0fd5 into coveragepy:main Aug 6, 2026
72 checks passed
@nedbat

nedbat commented Aug 6, 2026

Copy link
Copy Markdown
Member

This is now released as part of coverage 7.15.4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants