Loading a DLL with LoadLibraryA(path_to_dll) is changing the inherit handle flag (HANDLE_FLAG_INHERIT) from 1 to 0 for file descriptors 0, 1, and 2

Issue

We have written some functions in golang and a c wrapper on top of that to invoke those functions. We first build golang code to create an archive file and then we build the wrapper code in c to be consumed as a DLL.

After loading this DLL using LoadLibraryA(path_to_dll) in my program I am seeing that the inherit flags for fd 0, 1, and 2 are getting changed from 1 to 0. This does not happen immediately after loading the DLL though. I had added sleep in my code after the load library call and seems like it takes a few milliseconds after loading the library to change the flag values.

I am using GetHandleInformation((HANDLE) sock, &flags) to get the inherit flag value.

Any idea/pointers on what could be causing this? Thanks!

Updates:
I was able to find out the exact line in the go code that is flipping the inherit flag values. The global variable reqHandler in below k8sService.go code is causing this. Any idea why the use of this global variable is flipping the inherit flag values?

my-lib/k8sService.go (go code)

package main

import "C"
import (
    "my-lib/pkg/cmd"
)

func main() {
}

var reqHandler []*cmd.K8sRequest

my-lib/pkg/cmd/execute.go

import (
    "my-lib/pkg/dto"
)

type K8sRequest struct {
    K8sDetails dto.K8sDetails
}

my-lib/pkg/dto/structs.go

package dto

// K8sDetails contains all the necessary information about talking to the cluster. Below struct has few more variables.
type K8sDetails struct {
    // HostName of the cluster's API server
    HostName string `json:"hostname"`
    // Port on which the API server listens on to
    Port int `json:"port"`
}

We have a C wrapper on top of the above k8sService.go. We first build golang code to create an archive file and then with this archive file and wrapper code in C we build the target DLL. Below is the sample program which loads this DLL and also prints the inherit flag values before and after loading the DLL.

#include <windows.h>
#include <iostream>
#include <io.h>
#include "wrapper/cWrapper.h"

void printInheritVals() {
        typedef SOCKET  my_socket_t;
        my_socket_t fd0 = _get_osfhandle(0);
        my_socket_t fd1 = _get_osfhandle(1);
        my_socket_t fd2 = _get_osfhandle(2);
        std::cout << "fd0: " << fd0 << std::endl;
        std::cout << "fd1: " << fd1 << std::endl;
        std::cout << "fd2: " << fd2 << std::endl;
        
        
        DWORD flags;
        int inherit_flag_0 = -1;
        int inherit_flag_1 = -1;
        int inherit_flag_2 = -1;
        

        if (!GetHandleInformation((HANDLE) fd0, &flags)) {
            std::cout << "GetHandleInformation failed" << std::endl;
        } else {
            inherit_flag_0 = (flags & HANDLE_FLAG_INHERIT);
        }

        if (!GetHandleInformation((HANDLE) fd1, &flags)) {
            std::cout << "GetHandleInformation failed" << std::endl;
        } else {
            inherit_flag_1 = (flags & HANDLE_FLAG_INHERIT);
        }

        if (!GetHandleInformation((HANDLE) fd2, &flags)) {
            std::cout << "GetHandleInformation failed" << std::endl;
        } else {
            inherit_flag_2 = (flags & HANDLE_FLAG_INHERIT);
        }
        
        std::cout << "inherit_flag_0: " << inherit_flag_0 << std::endl;
        std::cout << "inherit_flag_1: " << inherit_flag_1 << std::endl;
        std::cout << "inherit_flag_2: " << inherit_flag_2 << std::endl;
}

int main()
{

    printInheritVals(); // In output all flag values are 1
    HINSTANCE hGetProcIDDLL = LoadLibraryA(PATH_TO_DLL);
    if (!hGetProcIDDLL) {
        std::cout << "could not load the dynamic library" << std::endl;
        return EXIT_FAILURE;
    }
    std::cout << "Library loaded" << std::endl;
    printInheritVals(); // In output all flag values are 1
    Sleep(1000);
    printInheritVals(); // In output all flag values are 0
    return EXIT_SUCCESS;
}

Solution

This is a bug in the golang.org/x/sys/windows package. The same issue used to be in the built-in syscall package as well, but it was fixed in Go 1.17.

Something in your project must be importing the golang.org/x version of the package instead of the built-in one, and so the following code executes to initialize the Stdin, Stdout, and Stderr variables:

var (
    Stdin  = getStdHandle(STD_INPUT_HANDLE)
    Stdout = getStdHandle(STD_OUTPUT_HANDLE)
    Stderr = getStdHandle(STD_ERROR_HANDLE)
)

func getStdHandle(stdhandle uint32) (fd Handle) {
    r, _ := GetStdHandle(stdhandle)
    CloseOnExec(r)
    return r
}

The fix for that code would be to remove the CloseOnExec call, which is what clears HANDLE_FLAG_INHERIT on the given file handle.

How to solve that in your project is less clear. I think you can vendor golang.org/x/sys module in your project, perhaps with a replace directive in your go.mod. Apply the fix in your local copy.

Meanwhile, I encourage you to also report the bug. The documentation instructs you to report the issue on the main Go project at GitHub, prefixing the title with x/sys.

Answered By – Rob Kennedy

Answer Checked By – Dawn Plyler (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.