A unit file reinterprets an environment value; a container does not
Found by being asked whether processes and containers handle environment the
same way. They do not, and the difference is not cosmetic.
Docker passes --env through literally. A unit file reads three things out of a
value that nothing else does, and a module's environment routinely contains all
three because a generated password is arbitrary bytes:
- % begins a specifier. %H is the hostname. A password containing one is
silently replaced, and it fails later as an authentication error nobody can
explain by reading the declaration.
- whitespace separates assignments. Unquoted, K=a b sets K to "a" and reads
"b" as another assignment.
- a newline ends the line, and what follows is read as a unit DIRECTIVE.
The first two are escaped: quoted, with quotes and backslashes escaped and
percent doubled. The third cannot be — a unit's environment has no way to carry
a line break — so it is refused in validation, near whoever wrote it. Without
that, an environment value could write ExecStart= and have the machine run
something nobody declared.
Ordinary awkward values stay accepted, because refusing those too would leave a
module unable to hold a generated password.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -502,6 +502,24 @@ func (d *Process) validate(where string, _ bool) []string {
|
||||
break
|
||||
}
|
||||
}
|
||||
// **A newline cannot be represented in a unit's environment, so it is refused rather than
|
||||
// mangled.** Everything else a unit file reinterprets — a percent specifier, whitespace
|
||||
// splitting assignments, a quote ending one early — can be escaped. A newline cannot: it ends
|
||||
// the line, and what follows is read as a unit DIRECTIVE. A value carrying one could write
|
||||
// ExecStart= and have the machine run something nobody declared.
|
||||
//
|
||||
// Refused here, near whoever wrote it, rather than at the far end of a declaration.
|
||||
for key, value := range d.Env {
|
||||
if strings.ContainsAny(value, "\n\r") {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: the value of %s contains a line break, which cannot be written into a unit's "+
|
||||
"environment — what followed it would be read as a unit directive", where, key))
|
||||
}
|
||||
if key == "" {
|
||||
problems = append(problems, where+": an environment value with no name")
|
||||
}
|
||||
}
|
||||
|
||||
if d.Schedule != "" {
|
||||
if d.RunOnce {
|
||||
problems = append(problems, where+
|
||||
|
||||
Reference in New Issue
Block a user