Jira (PUP-4369) generate() harmful in some circumstances, docs do not make this clear

17 views
Skip to first unread message

Tray Torrance (JIRA)

unread,
Apr 1, 2015, 8:06:15 PM4/1/15
to puppe...@googlegroups.com
Tray Torrance created an issue
 
Puppet / Bug PUP-4369
generate() harmful in some circumstances, docs do not make this clear
Issue Type: Bug Bug
Assignee: Unassigned
Created: 2015/04/01 5:05 PM
Priority: Normal Normal
Reporter: Tray Torrance

For background, we use generate very sparingly (3 places, specifically), but all 3 are related to fairly critical files. Last week, we had an outage which caused the script run via generate() to emit errors, which leaked into the managed file via this behavior. This resulted in a relatively large outage in a development environment.

I'm opening this ticket to request that either the behavior, or documentation, of generate() be changed. We were very thorough when writing the script that generate calls, but at no point did we expect for stderr to be combined into stdout. This implies (as we saw) that, for example, if the system cannot launch the command specified in generate(), then errors like "cannot fork process" can be emitted by generate, no matter how carefully stderr was redirected in the tool generate itself calls to. In the long term, we have decided it was best to move the functionality we used generate() for into a custom parser function, but I would love to help protect other administrators from inflicting this damage upon themselves unknowingly.

Add Comment Add Comment
 
This message was sent by Atlassian JIRA (v6.3.15#6346-sha1:dbc023d)
Atlassian logo

Tray Torrance (JIRA)

unread,
Apr 1, 2015, 8:10:11 PM4/1/15
to puppe...@googlegroups.com
Tray Torrance updated an issue
Change By: Tray Torrance
For background, we use generate very sparingly (3 places, specifically), but all 3 are related to fairly critical files. Last week, we had an outage which caused the script run via generate() to emit errors, which leaked into the managed file via [this behavior|https://github.com/puppetlabs/puppet/blob/ master 3.7.5 /lib/puppet/util/execution.rb# L135 L133 - L139 L137 ]. This resulted in a relatively large outage in a development environment.


I'm opening this ticket to request that either the behavior, or documentation, of generate() be changed. We were very thorough when writing the script that generate calls, but at no point did we expect for stderr to be combined into stdout. This implies (as we saw) that, for example, if the system cannot launch the command specified in generate(), then errors like "cannot fork process" can be emitted by generate, no matter how carefully stderr was redirected in the tool generate itself calls to. In the long term, we have decided it was best to move the functionality we used generate() for into a custom parser function, but I would love to help protect other administrators from inflicting this damage upon themselves unknowingly.

Tray Torrance (JIRA)

unread,
Apr 17, 2015, 11:45:49 PM4/17/15
to puppe...@googlegroups.com
Tray Torrance updated an issue
For background, we use generate very sparingly (3 places, specifically), but all 3 are related to fairly critical files. Last week  (under Puppet v3.7.4) , we had an outage which caused the script run via generate() to emit errors, which leaked into the managed file via [this behavior|https://github.com/puppetlabs/puppet/blob/3.7.5/lib/puppet/util/execution.rb#L133-L137]. This resulted in a relatively large outage in a development environment.


I'm opening this ticket to request that either the behavior, or documentation, of generate() be changed. We were very thorough when writing the script that generate calls, but at no point did we expect for stderr to be combined into stdout. This implies (as we saw) that, for example, if the system cannot launch the command specified in generate(), then errors like "cannot fork process" can be emitted by generate, no matter how carefully stderr was redirected in the tool generate itself calls to. In the long term, we have decided it was best to move the functionality we used generate() for into a custom parser function, but I would love to help protect other administrators from inflicting this damage upon themselves unknowingly.


EDIT: Updated to include our Puppet version, though this behavior seems to affect quite a wide range of versions

John Duarte (JIRA)

unread,
May 17, 2017, 1:52:04 PM5/17/17
to puppe...@googlegroups.com
John Duarte updated an issue
Change By: John Duarte
Labels: triaged
This message was sent by Atlassian JIRA (v6.4.14#64029-sha1:ae256fe)
Atlassian logo

Josh Cooper (JIRA)

unread,
May 17, 2017, 1:53:04 PM5/17/17
to puppe...@googlegroups.com
Josh Cooper commented on Bug PUP-4369
 
Re: generate() harmful in some circumstances, docs do not make this clear

I can't reproduce this:

[root@a31pq7go230crcw ~]# puppet apply -e "notify { generate('/bin/echoasd'): }"
Error: Evaluation Error: Error while evaluating a Function Call, Failed to execute generator /bin/echoasd: Execution of '/bin/echoasd' returned 1:   at line 1:10 on node a31pq7go230crcw.delivery.puppetlabs.net
[root@a31pq7go230crcw ~]# puppet apply -e "notify { generate('/bin/echoasd', '1'): }"
Error: Evaluation Error: Error while evaluating a Function Call, Failed to execute generator /bin/echoasd: Execution of '/bin/echoasd 1' returned 1:   at line 1:10 on node a31pq7go230crcw.delivery.puppetlabs.net
[root@a31pq7go230crcw ~]# puppet apply -e "notify { generate('/bin/echoasd', '1', '2'): }"
Error: Evaluation Error: Error while evaluating a Function Call, Failed to execute generator /bin/echoasd: Execution of '/bin/echoasd 1 2' returned 1:   at line 1:10 on node a31pq7go230crcw.delivery.puppetlabs.net

Note there is a related epic about rewriting Puppet::Util::Execution, and the linked behavior is one of the many reasons why.

Tray Torrance (JIRA)

unread,
May 17, 2017, 4:38:04 PM5/17/17
to puppe...@googlegroups.com
Tray Torrance commented on Bug PUP-4369

Josh Cooper Thank you for triaging this, however, I need to be clear: This is not about errors that directly result from the execution. To properly recreate the scenario I'm describing here, consider the following:

$value = generate('/path/to/something.sh')

Assume that the shell script is a bash script, and runs multiple additional external commands (not via bash's exec).

Now, assume that the puppetmaster on which this is evaluated is under extraordinary circumstances, and is rapidly approaching, eg, the nproc system limit on a Linux box (in the extreme case, assume we are at nproc-1). The following occurs:

  • generate calls the linked helper function, which launches the shell script's bash with stdout and stderr combined. The launched process itself is now running, and has not completed (and therefore not returned a failing error code)
  • bash attempts to launch an extra process (which would then be a child of this process, and inherit the stdout and stderr of its parent). This fails once we have reached nproc processes
  • The failure causes the bash process to emit something along the lines of Error: cannot fork to its stderr (which is combined with its stdout)
  • The bash process itself may continue to run, erroring all along the way, before eventually terminating (possibly with an acceptable return code, depending on the options set in the shell, and the code paths encountered)
  • If the shell script does exit with an acceptable return code, the output will contain the stderr emissions from the parent bash process

I realize that this is relatively difficult to synthesize a test for. My advice would be to set a very low nproc limit (the bare minimum needed to sustain the puppet run + a bit of overhead) and then write a shell script which launches enough processes (a call to a non-builtin sleep would probably be useful here) in the background to consume the overhead and cause the bash process to encounter failures. By making the last command in the script exit 0 or true I believe we can recreate the proper return code.

Matthias Hörmann (JIRA)

unread,
May 18, 2017, 4:06:02 AM5/18/17
to puppe...@googlegroups.com

Matthias Hörmann (JIRA)

unread,
May 18, 2017, 4:13:02 AM5/18/17
to puppe...@googlegroups.com

Moses Mendoza (JIRA)

unread,
May 18, 2017, 1:44:50 PM5/18/17
to puppe...@googlegroups.com

Eric Sorenson (JIRA)

unread,
Aug 14, 2017, 8:36:04 PM8/14/17
to puppe...@googlegroups.com

Maggie Dreyer (JIRA)

unread,
Sep 18, 2017, 5:36:03 PM9/18/17
to puppe...@googlegroups.com
Maggie Dreyer assigned an issue to Unassigned
Change By: Maggie Dreyer
Assignee: Josh Cooper

Maggie Dreyer (JIRA)

unread,
Sep 18, 2017, 5:36:04 PM9/18/17
to puppe...@googlegroups.com

Josh Cooper (Jira)

unread,
Jun 10, 2021, 7:40:02 PM6/10/21
to puppe...@googlegroups.com
Josh Cooper commented on Bug PUP-4369
 
Re: generate() harmful in some circumstances, docs do not make this clear

The generate function unfortunately doesn't pass combine: false when calling Puppet::Util::Execution.execute, so stdout & stderr are combined and can lead to surprises:

❯ cat -p generator.sh
#!/bin/sh
 
>&2 echo "error"
echo "success"
exit 0
 
❯ bx puppet apply -e "notice(generate('/home/josh/work/puppet/generator.sh'))"
Notice: Scope(Class[main]): error
success
 
Notice: Compiled catalog for ...

I can't think of any reason why someone might be relying on stdout and stderr to be combined, so the fix may be as simple as passing combine: false...

This message was sent by Atlassian Jira (v8.13.2#813002-sha1:c495a97)
Atlassian logo

Josh Cooper (Jira)

unread,
Jun 10, 2021, 11:17:03 PM6/10/21
to puppe...@googlegroups.com
Josh Cooper commented on Bug PUP-4369

In PUP-6949, it was suggested to have the function accept a lambda, and have stdout, stderr and exit code passed to it (more like popen). That's probably the ideal solution, while not combining stdout and stderr is a good start.

Reply all
Reply to author
Forward
0 new messages