Bug #120489 MgmApiSession queries peer address from a moved-from NdbSocket
Submitted: 17 May 16:24
Reporter: S F. Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Cluster: Cluster (NDB) storage engine Severity:S3 (Non-critical)
Version:MySQL 9.6.0, commit 447eb26e094b444a88c5 OS:Any
Assigned to: CPU Architecture:Any
Tags: mgmapi, ndb

[17 May 16:24] S F.
Description:
MgmApiSession::MgmApiSession() moves the accepted NdbSocket &&sock parameter into m_secure_socket, then later calls ndb_getpeername(sock.ndb_socket(), &addr) on the moved-from parameter.

NdbSocket move construction swaps the native socket handle into the destination and leaves the source object default-invalid. Therefore sock.ndb_socket() returns an invalid socket after the move. On POSIX this results in getpeername(-1, ...), which fails with EBADF.

As a result, the constructor does not resolve the peer address and m_name remains initialized as "unknown:0", making management API session logging/diagnostics misleading.

Relevant code path:

```cpp
MgmApiSession::MgmApiSession(class MgmtSrvr &mgm, NdbSocket &&sock,
                              Uint64 session_id)
    : SocketServer::Session(m_secure_socket),
      m_secure_socket(std::move(sock)),
      ...
      m_name("unknown:0") {
  ...
  ndb_sockaddr addr;
  if (ndb_getpeername(sock.ndb_socket(), &addr) == 0) {
    ...
    m_name.assfmt("%s", sockaddr_string);
  }
}
```

How to repeat:
The following simplified program reproduces the ownership transfer issue using the same operation sequence as MgmApiSession: move an NdbSocket, then call ndb_getpeername() on the moved-from object versus the moved-to object.

```cpp
#include "util/NdbSocket.h"

#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>

#include <iostream>

int main() {
  int listener = ::socket(AF_INET, SOCK_STREAM, 0);

  sockaddr_in addr{};
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  addr.sin_port = 0;

  ::bind(listener, reinterpret_cast<sockaddr *>(&addr), sizeof(addr));
  ::listen(listener, 1);

  socklen_t addr_len = sizeof(addr);
  ::getsockname(listener, reinterpret_cast<sockaddr *>(&addr), &addr_len);

  int client_fd = ::socket(AF_INET, SOCK_STREAM, 0);
  ::connect(client_fd, reinterpret_cast<sockaddr *>(&addr), addr_len);

  int server_fd = ::accept(listener, nullptr, nullptr);
  ::close(listener);

  NdbSocket sock(ndb_socket_create_from_native(server_fd));
  NdbSocket m_secure_socket(std::move(sock));

  ndb_sockaddr from_moved_param;
  errno = 0;
  int moved_result = ndb_getpeername(sock.ndb_socket(), &from_moved_param);
  int moved_errno = errno;

  ndb_sockaddr from_member;
  errno = 0;
  int member_result =
      ndb_getpeername(m_secure_socket.ndb_socket(), &from_member);
  int member_errno = errno;

  std::cout << "moved-from sock valid: " << sock.is_valid() << "\n";
  std::cout << "m_secure_socket valid: " << m_secure_socket.is_valid() << "\n";
  std::cout << "ndb_getpeername(sock.ndb_socket()) result: "
            << moved_result << ", errno: " << moved_errno << "\n";
  std::cout << "ndb_getpeername(m_secure_socket.ndb_socket()) result: "
            << member_result << ", errno: " << member_errno << "\n";

  ::close(client_fd);
  ::close(m_secure_socket.release_native_socket());
}
```

Observed output:

moved-from sock valid: 0
m_secure_socket valid: 1
ndb_getpeername(sock.ndb_socket()) result: 1, errno: 9
ndb_getpeername(m_secure_socket.ndb_socket()) result: 0, errno: 0

errno: 9 is EBADF, confirming that sock.ndb_socket() is invalid after std::move(sock), while m_secure_socket.ndb_socket() is valid.

Suggested fix:
Use the session-owned socket after the move:

```cpp
ndb_sockaddr addr;
if (ndb_getpeername(m_secure_socket.ndb_socket(), &addr) == 0) {
  char addr_buf[NDB_ADDR_STRLEN];
  char *addr_str = Ndb_inet_ntop(&addr, addr_buf, sizeof(addr_buf));
  char buf[512];
  char *sockaddr_string =
      Ndb_combine_address_port(buf, sizeof(buf), addr_str, addr.get_port());
  m_name.assfmt("%s", sockaddr_string);
}
```