Skip to content

cio: prevent NPE when closing, and fix pipes potentially not being closed on Windows #4980

Merged
estesp merged 4 commits intocontainerd:masterfrom
thaJeztah:prevent_cio_npe
Feb 4, 2021
Merged

cio: prevent NPE when closing, and fix pipes potentially not being closed on Windows #4980
estesp merged 4 commits intocontainerd:masterfrom
thaJeztah:prevent_cio_npe

Conversation

@thaJeztah
Copy link
Copy Markdown
Member

1. cio: FIFOSet.Close() check if FIFOSet is nill to prevent NPE

This relates to docker/for-linux#1186. Perhaps this needs synchronisation elsewhere, but looks like this could potentially panic here as well. Seems not harmful to check for nil.

2. cio: openFifos() use named return variables to use in defer()

This change is mostly defensive; when checking for the returned error, it's easy to make a mistake, and check for a "local" error, not the actual returned error.

This patch changes the function to use a named return variable, which is checked in the defer.

3. cio.copyIO: fix pipes potentially not being closed (Windows)

The defer functions were checking the local variable, and would therefore not be executed, as the function returned if an error occurred.

Perhaps best illustrated when renaming the local variables;

if fifos.Stdin != "" {
    l, err1 := winio.ListenPipe(fifos.Stdin, nil)
    if err1 != nil {
        return nil, errors.Wrapf(err1, "failed to create stdin pipe %s", fifos.Stdin)
    }
    defer func(l net.Listener) {
        if err1 != nil {
            l.Close()
        }
    }(l)
    // ...
}

if fifos.Stdout != "" {
    l, err2 := winio.ListenPipe(fifos.Stdout, nil)
    if err2 != nil {
        return nil, errors.Wrapf(err2, "failed to create stdout pipe %s", fifos.Stdout)
    }
    defer func(l net.Listener) {
        if err2 != nil {
            l.Close()
        }
    }(l)
    // ....
}

This patch changes the function to use a named return variable, and to use a single defer() that closes all pipes.

4. cio.copyIO: refactor to use cio.Close() (windows)

A follow-up to the patch above; Use the existing .Close() method instead of implementing the same
logic in this function.

The defer sets cios to nil if an error occurred to preserve the existing behavior.

@k8s-ci-robot
Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@theopenlab-ci
Copy link
Copy Markdown

theopenlab-ci Bot commented Jan 29, 2021

Build succeeded.

@thaJeztah thaJeztah marked this pull request as ready for review January 29, 2021 12:48
@thaJeztah
Copy link
Copy Markdown
Member Author

@cpuguy83 @AkihiroSuda ptal 🤗

Comment thread cio/io_windows.go Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler to not use a named return value for cio (_ *cio, retErr error), then just return nil here instead? Seems like we would prefer to return nil when we have an error, and I think we're only returning this due to the named return value.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's roughly what's in the 3rd commit, but does mean that we have to do the closing by iterating over the set slice; I thought we might as well use cio.Close() to do that for us (and skip the intermediate set slice)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! I think I understand what you mean; so make cios a local variable (but still use it in the defer); is that what you meant?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, something like this:

func copyIO(fifos *FIFOSet, ioset *Streams) (_ *cio, retErr error) { // cios changed to _
	cios := &cio{config: fifos.Config} // = changed to :=

[...]

			return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdin)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That also lets you simplify the defer to just:

	defer func() {
		if retErr != nil {
			_ = cios.Close()
		}
	}()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, I like that.

technically, we shouldn't have to set it to nil as no consumer should handle the value on error, but I wanted to keep the behavior as it was, just in case.

I'll have a look at updating the PR; thanks!

This change is mostly defensive; when checking for the returned
error, it's easy to make a mistake, and check for a "local" error,
not the actual returned error.

This patch changes the function to use a named return variable,
which is checked in the defer.

Signed-off-by: Sebastiaan van Stijn <[email protected]>
The defer functions were checking the local variable, and would therefore
not be executed, as the function returned if an error occurred.

Perhaps best illustrated when renaming the local variables;

    if fifos.Stdin != "" {
        l, err1 := winio.ListenPipe(fifos.Stdin, nil)
        if err1 != nil {
            return nil, errors.Wrapf(err1, "failed to create stdin pipe %s", fifos.Stdin)
        }
        defer func(l net.Listener) {
            if err1 != nil {
                l.Close()
            }
        }(l)
        // ...
    }

    if fifos.Stdout != "" {
        l, err2 := winio.ListenPipe(fifos.Stdout, nil)
        if err2 != nil {
            return nil, errors.Wrapf(err2, "failed to create stdout pipe %s", fifos.Stdout)
        }
        defer func(l net.Listener) {
            if err2 != nil {
                l.Close()
            }
        }(l)
        // ....
    }

This patch changes the function to use a named return variable, and to use
a single `defer()` that closes all pipes.

Signed-off-by: Sebastiaan van Stijn <[email protected]>
Use the existing `.Close()` method instead of implementing the same
logic in this function.

The defer sets `cios` to `nil` if an error occurred to preserve the
existing behavior.

Signed-off-by: Sebastiaan van Stijn <[email protected]>
@theopenlab-ci
Copy link
Copy Markdown

theopenlab-ci Bot commented Feb 1, 2021

Build succeeded.

@thaJeztah
Copy link
Copy Markdown
Member Author

@kevpar updated; ptal 👍

Copy link
Copy Markdown
Member

@kevpar kevpar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@thaJeztah
Copy link
Copy Markdown
Member Author

@estesp @AkihiroSuda ptal 🤗

Copy link
Copy Markdown
Member

@estesp estesp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@estesp estesp merged commit aa5e55a into containerd:master Feb 4, 2021
@thaJeztah thaJeztah deleted the prevent_cio_npe branch February 4, 2021 20:29
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.

4 participants