Kernel Control KPI

The kernel control interface <sys/kern_control.h> is a KPI, which allows a KEXT to communicate bi-directionally with user space processes. The kernel control system lives in the BSD portion of the kernel and is therefore written in C and not C++ (I/O Kit uses C++).

The KPI is intended to allow a user space program to control and configure a KEXT. For example, let's say you had implemented a custom firewall NKE (Network Kernel Extension). You could then use the kernel control API to tell your firewall which addresses or ports it should block traffic from, as well as retrieving logs and statistics.

The KPI is relatively simple to use in both kernel space and user space. In fact, there is no special API required to use the kernel control mechanism for user space, because it is accessed via a regular socket. The getsockopt() or setsockopt() system call functions can be used to issue control requests from user space. The kernel control system may be compared to the ioctl() system call, but unlike the ioctl() system call the kernel control system is better suited for transferring large amounts of data across the kernel/user space boundary. Sending and receiving data are supported using the send() and recv() system call functions from user space. In the kernel, data transfers are handled using the mbuf data structure discussed earlier.

To use the kernel control interface, you must first register a new interface, which ensures that user space clients can find it and connect to it. This is accomplished by declaring and filling out a C structure containing callbacks for various events, as well as an identifying name. The C language is not object-oriented, and therefore “objects” are often represented by structures containing data and function pointers. The registration structure is shown in Listing 17-1.

Listing 17-1. The kern_ctl_reg Structure from <sys/kern_control.h>

struct kern_ctl_reg
{
    /* control information */
    char                        ctl_name[MAX_KCTL_NAME];
    u_int32_t                   ctl_id;
    u_int32_t                   ctl_unit;
    /* control settings */
    u_int32_t                   ctl_flags;
    u_int32_t                   ctl_sendsize;
    u_int32_t                   ctl_recvsize;
    /* Dispatch functions */
    ctl_connect_func            ctl_connect;
    ctl_disconnect_func         ctl_disconnect;
    ctl_send_func               ctl_send;
    ctl_setopt_func             ctl_setopt;
    ctl_getopt_func             ctl_getopt;
};

Let's look at the fields of the structure in more detail:

  • The ctl_name should be set to the bundle ID for the KEXT.
  • The ctl_id field is used for additional addressing because a KEXT may have several kernel controls registered at once. The ctl_id field can be dynamically registered or assigned by Apple's developer technical support (DTS).
  • The ctl_unit field is used only with a DTS-assigned ID. There are only two flags for the ctl_flags. The first is CTL_FLAG_PRIVILEGED, which if set means that a user space program must have root privileges in order to connect to the kernel control. The second flag is CTL_FLAG_REG_ID_UNIT, which should be set if using a DTS assigned ID.
  • The ctl_sendsize and ctl_recvsize fields can be used to tune the size of the send and receive buffers for sending data using send() and recv().

The remaining fields are function pointers, which will be called when their corresponding events occur:

  • The ctl_connect and ctl_disconnect callbacks will be called when a user space client connects or disconnects.
  • The ctl_setopt and ctl_getopt callbacks are invoked when a client uses the setsockopt() or getsockopt() functions. These are often used to get or set configuration parameters. The next callback is ctl_send, which may be a bit confusing, as it's used not to send data but to receive data from a sending client. To actually send data, use the ctl_enqueuedata() function.

Kernel Control Registration

Let's look at an example (HelloKernControl) of how a kernel control interface is used. In this example, you will implement a very minimal kernel control with one get and one set operation. The get operation returns a string stored in the kernel. The set operation overwrites this string so that subsequent get operations return the new string instead. The following is an example of a filled out kernel control registration structure:

static struct kern_ctl_reg g_kern_ctl_reg = {
    "com.osxkernel.HelloKernControl",              
    0,
    0,
    CTL_FLAG_PRIVILEGED,
    0,
    0,
    hello_ctl_connect,
    hello_ctl_disconnect,
    NULL,
    hello_ctl_set,
    hello_ctl_get
};

We use a dynamically assigned ID and specify that our kernel control will be accessible only by privileged clients (root). We defined four callbacks, but we leave the ctl_send callback as NULL because we don't support it in this example. The following is the code used to register and deregister the kernel control:

static boolean_t g_filter_registered = FALSE;
static kern_ctl_ref g_ctl_ref;

kern_return_t HelloKernControl_start (kmod_info_t* ki, void* d)

{    
    strncpy(g_string_buf, DEFAULT_STRING, strlen(DEFAULT_STRING));
    
    /* Register the control */
    int ret = ctl_register(&g_kern_ctl_reg, &g_ctl_ref);
    
    if (ret == KERN_SUCCESS)
    {
        g_filter_registered = TRUE;
        return KERN_SUCCESS;
    }
    return KERN_FAILURE;
}

kern_return_t HelloKernControl_stop (kmod_info_t* ki, void* d)
{    
    if (g_clients_connected != 0)
        return KERN_FAILURE;
    
    if (g_filter_registered)
        ctl_deregister(g_ctl_ref);
    
    return KERN_SUCCESS;
}

You register the interface in the KEXT's start() function and deregister it in the stop() function, which will be called before the KEXT is unloaded. Because a kernel control often shares some data with user space, it is necessary to define a shared header file to store common declarations used by both the kernel and user space. The shared header file for HelloKernControl is shown in the following example:

#ifndef HelloKernControl_HelloKernControl_h
#define HelloKernControl_HelloKernControl_h

#define BUNDLE_ID "com.osxkernel.HelloKernControl"

#define HELLO_CONTROL_GET_STRING  1
#define HELLO_CONTROL_SET_STRING  2

#define DEFAULT_STRING            "Hello World"
#define MAX_STRING_LEN            256

#endif

Client Connections

The following are the implementation of the connect and disconnect callbacks:

static int hello_ctl_connect(kern_ctl_ref ctl_ref, struct sockaddr_ctl *sac, void** unitinfo)
{
    printf("process with pid=%d connected ", proc_selfpid());
    return 0;
}
static errno_t hello_ctl_disconnect(kern_ctl_ref ctl_ref, u_int32_t unit, void* unitinfo)
{    
    printf("process with pid=%d disconnected ", proc_selfpid());
    return 0;
}

In the preceding example the hello_ctl_connect() function, logs the PID of the client that opened the kernel control. It is often necessary to maintain some per-client data structure. The data structure should be assigned to the unitinfo parameter—for example: *uinitinfo = myStructure;. The structure can now be retrieved in other callbacks. If you allocate memory when the client connects, you should free the memory in the disconnect callback. If you wish to refuse a client—for example, because only one client is allowed at a time, or the maximum number of clients is already connected—you can simply return an error code, such as EBUSY or EPERM.

Getting and Setting Options

Once a client is successfully connected, it can start issuing get/set option requests to the kernel control. The implementation of the control get function is as follows:

static int hello_ctl_get(kern_ctl_ref ctl_ref, u_int32_t unit, void *unitinfo, int opt,
                         void *data, size_t *len)
{
    int ret = 0;
    switch (opt) {
        case HELLO_CONTROL_GET_STRING:
            *len = min(MAX_STRING_LEN, *len);
            strncpy(data, g_string_buf, *len);
            break;
        default:
            ret = ENOTSUP;
            break;
    }
    return ret;
}

The opt argument comes from the client and specifies which option the client is interested in. A common approach is to create a shared header file, which contains option definitions that are shared between the KEXT and the user space program.

images Caution Be careful about sharing data structures, because the KEXT and the user space program may pad the structure differently. This can cause bugs, corruptions, or worse.

The preceding case only handles one option. This option is defined by HELLO_CONTROL_GET_STRING, which returns the string in the global g_string_buf variable shared between all clients. If you had allocated private data during the connect callback, you could retrieve it by casting the type of the data from the unitinfo argument.

To return the string to the client, you will copy it to the memory address given in the data argument. The len argument is an input/output argument and contains the length of the data buffer. Obviously, you must ensure that you do not write out of bounds. If you write to the buffer, you should modify len to reflect how many bytes were actually written.

The implementation of the set option function is very similar:

static int hello_ctl_set(kern_ctl_ref ctl_ref, u_int32_t unit, void* unitinfo, int opt,
                         void* data, size_t len)
{
    int ret = 0;
    switch (opt) {
        case HELLO_CONTROL_SET_STRING:
            strncpy(g_string_buf, (char*)data, min(MAX_STRING_LEN, len));
            printf("HELLP_CONTROL_SET_STRING: new string set to: "%s" ", g_string_buf);
            break;
       default:
            ret = ENOTSUP;
            break;
   }
   return ret;
}

As with the control get option function, we are passed a buffer with data coming from user space and the length of the buffer in the data and len arguments. The data is not valid once the function returns, so you must copy any data you want to preserve.

Accessing Kernel Controls from User Space

The example in Listing 17-2 demonstrates how we can connect the kernel control interface described in the previous sections.

Listing 17-2. User Space Tool for Connecting to a Kernel Control Interface

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/kern_control.h>
#include <sys/sys_domain.h>

#include "HelloKernControl.h"

int main(int argc, char* const*argv)
{
    struct ctl_info ctl_info;
    struct sockaddr_ctl sc;
    char str[MAX_STRING_LEN];
    
    int sock = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
       if (sock < 0)
       return -1;

    bzero(&ctl_info, sizeof(struct ctl_info));
    strcpy(ctl_info.ctl_name, "com.osxkernel.HelloKernControl");
    
    if (ioctl(sock, CTLIOCGINFO, &ctl_info) == -1)
       return -1;
    
    bzero(&sc, sizeof(struct sockaddr_ctl));
    sc.sc_len = sizeof(struct sockaddr_ctl);
    sc.sc_family = AF_SYSTEM;
    sc.ss_sysaddr = SYSPROTO_CONTROL;
    sc.sc_id = ctl_info.ctl_id;
    sc.sc_unit = 0;
    
    if (connect(sock, (struct sockaddr *)&sc, sizeof(struct sockaddr_ctl)))
        return -1;
    
    /* Get an existing string from the kernel */
    unsigned int size = MAX_STRING_LEN;
    if (getsockopt(sock, SYSPROTO_CONTROL, HELLO_CONTROL_GET_STRING, &str, &size) == -1)
        return -1;
    
    printf("kernel string is: %s ", str);
    
    /* Set a new string */
    strcpy(str, "Hello Kernel, here's your new string, enjoy!");
    if (setsockopt(sock, SYSPROTO_CONTROL, HELLO_CONTROL_SET_STRING,
                   str, (socklen_t)strlen(str)) == -1)
        return -1;

    close(sock);

    return 0;
}

When the program in Listing 17-2 is executed, you should see the following results:


$ sudo kextload HelloKernControl.kext

$ sudo ./hello_tool
kernel string is: Hello World
$ sudo ./hello_tool
kernel string is: Hello Kernel, here's your new string, enjoy!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.245.219