Skip to content

crypto/tls: Conn.handshakeFn cause memory leak #41987

Description

@For-ACGN

What version of Go are you using (go version)?

$ go version
go version go1.15.3 windows/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
windows 10 1909, amd64

What did you do?

check tls.Conn is destroyed(object unreachable)

What did you expect to see?

destroyed(object unreachable)

What did you see instead?

not destroyed(object reachable)

test code

// destroyed is used to check object is destroyed.
func destroyed(object interface{}) bool {
	destroyed := make(chan struct{})
	runtime.SetFinalizer(object, func(interface{}) {
		close(destroyed)
	})
	// total 3 seconds
	timer := time.NewTimer(10 * time.Millisecond)
	defer timer.Stop()
	for i := 0; i < 300; i++ {
		timer.Reset(10 * time.Millisecond)
		runtime.GC()
		select {
		case <-destroyed:
			return true
		case <-timer.C:
		}
	}
	return false
}


func TestTLSConn(t *testing.T) {
	gm := testsuite.MarkGoroutines(t)
	defer gm.Compare()

	serverCfg, clientCfg := testsuite.TLSConfigPair(t, "127.0.0.1")
	const network = "tcp"

	listener, err := tls.Listen(network, "127.0.0.1:0", serverCfg)
	require.NoError(t, err)
	address := listener.Addr().String()
	go func() {
		conn, err := listener.Accept()
		require.NoError(t, err)

		_, err = conn.Write([]byte("data"))
		require.NoError(t, err)

		time.Sleep(time.Second)

		err = conn.Close()
		require.NoError(t, err)
	}()

	conn, err := tls.Dial(network, address, clientCfg)
	require.NoError(t, err)

	buf := make([]byte, 4)
	_, err = io.ReadFull(conn, buf)
	require.NoError(t, err)

	err = conn.Close()
	require.NoError(t, err)

	require.True(t, destroyed(conn)) // here
}

tls.Conn is not destroyed

// crypto/tls/conn.go:

// A Conn represents a secured connection.
// It implements the net.Conn interface.
type Conn struct {
	// constant
	conn        net.Conn
	isClient    bool
	handshakeFn func() error // (*Conn).clientHandshake or serverHandshake
	// handshakeFn will reference self
	// ...
}

// crypto/tls/tls.go:

// Server returns a new TLS server side connection
// using conn as the underlying transport.
// The configuration config must be non-nil and must include
// at least one certificate or else set GetCertificate.
func Server(conn net.Conn, config *Config) *Conn {
	c := &Conn{
		conn:   conn,
		config: config,
	}
	c.handshakeFn = c.serverHandshake // reference self
	return c
}

// Client returns a new TLS client side connection
// using conn as the underlying transport.
// The config cannot be nil: users must set either ServerName or
// InsecureSkipVerify in the config.
func Client(conn net.Conn, config *Config) *Conn {
	c := &Conn{
		conn:     conn,
		config:   config,
		isClient: true,
	}
	c.handshakeFn = c.clientHandshake // reference self
	return c
}

fix it that don't use handshakeFn.

// crypto/tls/conn.go:

func (c *Conn) Handshake() error {
	c.handshakeMutex.Lock()
	defer c.handshakeMutex.Unlock()

	if err := c.handshakeErr; err != nil {
		return err
	}
	if c.handshakeComplete() {
		return nil
	}

	c.in.Lock()
	defer c.in.Unlock()

	// use it
	if c.isClient {
		c.handshakeErr = c.clientHandshake()
	} else {
		c.handshakeErr = c.serverHandshake()
	}
	// c.handshakeErr = c.handshakeFn()
	if c.handshakeErr == nil {
		c.handshakes++
	} else {
		// If an error occurred during the handshake try to flush the
		// alert that might be left in the buffer.
		c.flush()
	}

	if c.handshakeErr == nil && !c.handshakeComplete() {
		c.handshakeErr = errors.New("tls: internal error: handshake should have had a result")
	}

	return c.handshakeErr
}

fix it and TestTLSConn is passed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions