Skip to content

[Correctness] ProcessHelper.EscapeArgument — fails to double backslashes that precede an internal quote (Win32 CommandLineToArgvW corruption) #827

@Christophe-Rogiers

Description

@Christophe-Rogiers

Severity: Warning
File: src/Servy.Core/Helpers/ProcessHelper.cs
Lines: 396-413

public string EscapeArgument(string arg)
{
    if (string.IsNullOrEmpty(arg)) return \"\\\"\\\"\";

    string escaped = arg.Replace(\"\\\"\", \"\\\\\\\"\");

    if (escaped.EndsWith(\"\\\\\"))
    {
        escaped += \"\\\\\";
    }

    return $\"\\\"{escaped}\\\"\";
}

The Win32 CommandLineToArgvW rule is: any run of N backslashes immediately before a \" must be doubled to 2N so the kernel parser sees 2N literal backslashes followed by an escaped quote. This method only doubles trailing backslashes and only escapes the quote character itself — it does not double backslashes that precede an internal quote.

Failure case: input foo\\\"bar (one backslash, one quote, then bar).

  • Replace(\"\\\"\", \"\\\\\\\"\")foo\\\\\"bar (now \\\\\").
  • Doesn't end with \, no trailing fix.
  • Final result: \"foo\\\\\"bar\".

CommandLineToArgvW parses this as: \\ → one literal \, then \" → closing quote. Result: argument is foo\ and bar becomes a separate argument (or junk).

Correct expectation: argument should be foo\\\"bar (the original literal). Required output is \"foo\\\\\\\"bar\" — two backslashes (so the kernel sees one literal \), then \\\" (escaped quote).

The sibling method EscapeProcessArgument (lines 350-393) implements this correctly. The two methods are exposed on the same IProcessHelper interface, so callers can pick either.

The buggy method is in use: ServiceCommands.cs:215 calls _processHelper.EscapeArgument(service.Name). Service names rarely contain \ or \", but other future callers may pass arbitrary text.

Suggested fix: delete EscapeArgument and route all callers to EscapeProcessArgument, or fix the body to mirror the per-character backslash-counting logic from EscapeProcessArgument.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions