Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API versioning for user/kernel boundary #39

Merged
merged 2 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions driver/API_VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
18 changes: 18 additions & 0 deletions driver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ endif()
# after the build we copy the compiled module one directory up,
# to ${CMAKE_CURRENT_BINARY_DIR}.

file(STRINGS API_VERSION PROBE_API_VERSION LIMIT_COUNT 1)
string(REGEX MATCHALL "[0-9]+" PROBE_API_COMPONENTS "${PROBE_API_VERSION}")
list(GET PROBE_API_COMPONENTS 0 PPM_API_CURRENT_VERSION_MAJOR)
list(GET PROBE_API_COMPONENTS 1 PPM_API_CURRENT_VERSION_MINOR)
list(GET PROBE_API_COMPONENTS 2 PPM_API_CURRENT_VERSION_PATCH)
message(STATUS "Probe API version ${PPM_API_CURRENT_VERSION_MAJOR}.${PPM_API_CURRENT_VERSION_MINOR}.${PPM_API_CURRENT_VERSION_PATCH}")

file(STRINGS SCHEMA_VERSION PROBE_SCHEMA_VERSION LIMIT_COUNT 1)
string(REGEX MATCHALL "[0-9]+" PROBE_SCHEMA_COMPONENTS "${PROBE_SCHEMA_VERSION}")
list(GET PROBE_SCHEMA_COMPONENTS 0 PPM_SCHEMA_CURRENT_VERSION_MAJOR)
list(GET PROBE_SCHEMA_COMPONENTS 1 PPM_SCHEMA_CURRENT_VERSION_MINOR)
list(GET PROBE_SCHEMA_COMPONENTS 2 PPM_SCHEMA_CURRENT_VERSION_PATCH)
message(STATUS "Probe schema version ${PPM_SCHEMA_CURRENT_VERSION_MAJOR}.${PPM_SCHEMA_CURRENT_VERSION_MINOR}.${PPM_SCHEMA_CURRENT_VERSION_PATCH}")

execute_process(COMMAND git rev-parse HEAD OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
string(STRIP "${GIT_COMMIT}" GIT_COMMIT)

configure_file(dkms.conf.in src/dkms.conf)
configure_file(Makefile.in src/Makefile)
configure_file(driver_config.h.in src/driver_config.h)
Expand All @@ -51,6 +68,7 @@ set(DRIVER_SOURCES
kernel_hacks.h
main.c
ppm.h
ppm_api_version.h
ppm_events.c
ppm_events.h
ppm_events_public.h
Expand Down
25 changes: 25 additions & 0 deletions driver/README.API_VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# API version number

The file API_VERSION must contain a semver-like version number of the userspace<->kernel API. All other lines are ignored.

## When to increment

**major version**: increment when the probe API becomes incompatible with previous userspace versions

**minor version**: increment when new features are added but existing features remain compatible

**patch version**: increment when code changes don't break compatibility (e.g. bug fixes)

Do *not* increment for patches that only add support for new kernels, without affecting already supported ones.

# Schema version number

The file SCHEMA_VERSION must contain a semver-like version number of the event schema. All other lines are ignored.

## When to increment

**major version**: increment when the schema becomes incompatible with previous userspace versions

**minor version**: increment when new features are added but existing features remain compatible (e.g. new event fields or new events)

**patch version**: increment when code changes don't break compatibility (e.g. bug fixes in filler code)
1 change: 1 addition & 0 deletions driver/SCHEMA_VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
6 changes: 6 additions & 0 deletions driver/bpf/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,9 @@ char kernel_ver[] __bpf_section("kernel_version") = UTS_RELEASE;
char __license[] __bpf_section("license") = "GPL";

char probe_ver[] __bpf_section("probe_version") = PROBE_VERSION;

char probe_commit[] __bpf_section("build_commit") = PROBE_COMMIT;

uint64_t probe_api_ver __bpf_section("api_version") = PPM_API_CURRENT_VERSION;

uint64_t probe_schema_ver __bpf_section("schema_version") = PPM_SCHEMA_CURRENT_VERSION;
14 changes: 14 additions & 0 deletions driver/driver_config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,26 @@ or GPL2.txt for full copies of the license.
*/
#pragma once

/* taken from driver/API_VERSION */
#define PPM_API_CURRENT_VERSION_MAJOR ${PPM_API_CURRENT_VERSION_MAJOR}
#define PPM_API_CURRENT_VERSION_MINOR ${PPM_API_CURRENT_VERSION_MINOR}
#define PPM_API_CURRENT_VERSION_PATCH ${PPM_API_CURRENT_VERSION_PATCH}

/* taken from driver/SCHEMA_VERSION */
#define PPM_SCHEMA_CURRENT_VERSION_MAJOR ${PPM_SCHEMA_CURRENT_VERSION_MAJOR}
#define PPM_SCHEMA_CURRENT_VERSION_MINOR ${PPM_SCHEMA_CURRENT_VERSION_MINOR}
#define PPM_SCHEMA_CURRENT_VERSION_PATCH ${PPM_SCHEMA_CURRENT_VERSION_PATCH}

#include "ppm_api_version.h"

#define PROBE_VERSION "${PROBE_VERSION}"

#define PROBE_NAME "${PROBE_NAME}"

#define PROBE_DEVICE_NAME "${PROBE_DEVICE_NAME}"

#define PROBE_COMMIT "${GIT_COMMIT}"

#ifndef KBUILD_MODNAME
#define KBUILD_MODNAME PROBE_NAME
#endif
9 changes: 9 additions & 0 deletions driver/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,12 @@ static long ppm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
}
ret = 0;
goto cleanup_ioctl_nolock;
} else if (cmd == PPM_IOCTL_GET_API_VERSION) {
ret = PPM_API_CURRENT_VERSION;
goto cleanup_ioctl_nolock;
} else if (cmd == PPM_IOCTL_GET_SCHEMA_VERSION) {
ret = PPM_SCHEMA_CURRENT_VERSION;
goto cleanup_ioctl_nolock;
}

mutex_lock(&g_consumer_mutex);
Expand Down Expand Up @@ -2632,6 +2638,9 @@ void sysdig_exit(void)
module_init(sysdig_init);
module_exit(sysdig_exit);
MODULE_VERSION(PROBE_VERSION);
MODULE_INFO(build_commit, PROBE_COMMIT);
MODULE_INFO(api_version, PPM_API_CURRENT_VERSION_STRING);
MODULE_INFO(schema_version, PPM_SCHEMA_CURRENT_VERSION_STRING);

module_param(max_consumers, uint, 0444);
MODULE_PARM_DESC(max_consumers, "Maximum number of consumers that can simultaneously open the devices");
Expand Down
48 changes: 48 additions & 0 deletions driver/ppm_api_version.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef PPM_API_VERSION_H
#define PPM_API_VERSION_H

/*
* API version component macros
*
* The version is a single uint64_t, structured as follows:
* bit 63: unused (so the version number is always positive)
* bits 44-62: major version
* bits 24-43: minor version
* bits 0-23: patch version
*/

/* extract components from an API version number */
#define PPM_API_VERSION_MAJOR(ver) ((((ver) >> 44)) & (((1 << 19) - 1)))
#define PPM_API_VERSION_MINOR(ver) (((ver) >> 24) & (((1 << 20) - 1)))
#define PPM_API_VERSION_PATCH(ver) (((ver) & ((1 << 24) - 1)))

/* build an API version number from components */
#define PPM_API_VERSION(major, minor, patch) \
(((major) & (((1ULL << 19) - 1) << 44)) | \
((minor) & (((1ULL << 20) - 1) << 24)) | \
((major) & (((1ULL << 24) - 1))))

#define PPM_API_CURRENT_VERSION PPM_API_VERSION( \
PPM_API_CURRENT_VERSION_MAJOR, \
PPM_API_CURRENT_VERSION_MINOR, \
PPM_API_CURRENT_VERSION_PATCH)

#define PPM_SCHEMA_CURRENT_VERSION PPM_API_VERSION( \
PPM_SCHEMA_CURRENT_VERSION_MAJOR, \
PPM_SCHEMA_CURRENT_VERSION_MINOR, \
PPM_SCHEMA_CURRENT_VERSION_PATCH)

#define __PPM_STRINGIFY1(x) #x
#define __PPM_STRINGIFY(x) __PPM_STRINGIFY1(x)

#define PPM_API_CURRENT_VERSION_STRING \
__PPM_STRINGIFY(PPM_API_CURRENT_VERSION_MAJOR) "." \
__PPM_STRINGIFY(PPM_API_CURRENT_VERSION_MINOR) "." \
__PPM_STRINGIFY(PPM_API_CURRENT_VERSION_PATCH)

#define PPM_SCHEMA_CURRENT_VERSION_STRING \
__PPM_STRINGIFY(PPM_SCHEMA_CURRENT_VERSION_MAJOR) "." \
__PPM_STRINGIFY(PPM_SCHEMA_CURRENT_VERSION_MINOR) "." \
__PPM_STRINGIFY(PPM_SCHEMA_CURRENT_VERSION_PATCH)

#endif
2 changes: 2 additions & 0 deletions driver/ppm_events_public.h
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,8 @@ struct ppm_evt_hdr {
#define PPM_IOCTL_GET_PROBE_VERSION _IO(PPM_IOCTL_MAGIC, 21)
#define PPM_IOCTL_SET_FULLCAPTURE_PORT_RANGE _IO(PPM_IOCTL_MAGIC, 22)
#define PPM_IOCTL_SET_STATSD_PORT _IO(PPM_IOCTL_MAGIC, 23)
#define PPM_IOCTL_GET_API_VERSION _IO(PPM_IOCTL_MAGIC, 24)
#define PPM_IOCTL_GET_SCHEMA_VERSION _IO(PPM_IOCTL_MAGIC, 25)
#endif // CYGWING_AGENT

extern const struct ppm_name_value socket_families[];
Expand Down
10 changes: 10 additions & 0 deletions userspace/libscap/scap-int.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ struct scap

// The return value from the last call to next_batch().
ss_plugin_rc m_input_plugin_last_batch_res;

// API version supported by the probe
// If the API version is unavailable for whatever reason,
// it's equivalent to version 0.0.0
uint64_t m_api_version;

// schema version supported by the probe
// If the schema version is unavailable for whatever reason,
// it's equivalent to version 0.0.0
uint64_t m_schema_version;
};

typedef enum ppm_dumper_type
Expand Down
133 changes: 132 additions & 1 deletion userspace/libscap/scap.c
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,8 @@ scap_t* scap_open_live_int(char *error, int32_t *rc,
{
int len;
uint32_t all_scanned_devs;
uint64_t api_version;
uint64_t schema_version;

//
// Allocate the device descriptors.
Expand Down Expand Up @@ -427,10 +429,72 @@ scap_t* scap_open_live_int(char *error, int32_t *rc,
// Set close-on-exec for the fd
if (fcntl(handle->m_devs[j].m_fd, F_SETFD, FD_CLOEXEC) == -1) {
snprintf(error, SCAP_LASTERR_SIZE, "Can not set close-on-exec flag for fd for device %s (%s)", filename, scap_strerror(handle, errno));
close(handle->m_devs[j].m_fd);
scap_close(handle);
*rc = SCAP_FAILURE;
return NULL;
}

// Check the API version reported
api_version = ioctl(handle->m_devs[j].m_fd, PPM_IOCTL_GET_API_VERSION, 0);
if ((int64_t)api_version < 0)
{
snprintf(error, SCAP_LASTERR_SIZE, "Kernel module does not support PPM_IOCTL_GET_API_VERSION");
close(handle->m_devs[j].m_fd);
scap_close(handle);
*rc = SCAP_FAILURE;
return NULL;
}
// Make sure all devices report the same API version
if (handle->m_api_version != 0 && handle->m_api_version != api_version)
FedeDP marked this conversation as resolved.
Show resolved Hide resolved
{
snprintf(error, SCAP_LASTERR_SIZE, "API version mismatch: device %s reports API version %lu.%lu.%lu, expected %lu.%lu.%lu",
filename,
PPM_API_VERSION_MAJOR(api_version),
PPM_API_VERSION_MINOR(api_version),
PPM_API_VERSION_PATCH(api_version),
PPM_API_VERSION_MAJOR(handle->m_api_version),
PPM_API_VERSION_MINOR(handle->m_api_version),
PPM_API_VERSION_PATCH(handle->m_api_version)
);
close(handle->m_devs[j].m_fd);
scap_close(handle);
*rc = SCAP_FAILURE;
return NULL;
}
// Set the API version from the first device
// (for subsequent devices it's a no-op thanks to the check above)
handle->m_api_version = api_version;

// Check the schema version reported
schema_version = ioctl(handle->m_devs[j].m_fd, PPM_IOCTL_GET_SCHEMA_VERSION, 0);
if ((int64_t)schema_version < 0)
{
snprintf(error, SCAP_LASTERR_SIZE, "Kernel module does not support PPM_IOCTL_GET_SCHEMA_VERSION");
close(handle->m_devs[j].m_fd);
scap_close(handle);
*rc = SCAP_FAILURE;
return NULL;
}
// Make sure all devices report the same schema version
if (handle->m_schema_version != 0 && handle->m_schema_version != schema_version)
{
snprintf(error, SCAP_LASTERR_SIZE, "Schema version mismatch: device %s reports schema version %lu.%lu.%lu, expected %lu.%lu.%lu",
filename,
PPM_API_VERSION_MAJOR(schema_version),
PPM_API_VERSION_MINOR(schema_version),
PPM_API_VERSION_PATCH(schema_version),
PPM_API_VERSION_MAJOR(handle->m_schema_version),
PPM_API_VERSION_MINOR(handle->m_schema_version),
PPM_API_VERSION_PATCH(handle->m_schema_version)
);
scap_close(handle);
*rc = SCAP_FAILURE;
return NULL;
}
// Set the schema version from the first device
// (for subsequent devices it's a no-op thanks to the check above)
handle->m_schema_version = schema_version;

//
// Map the ring buffer
Expand Down Expand Up @@ -496,6 +560,34 @@ scap_t* scap_open_live_int(char *error, int32_t *rc,
}
}

if(!scap_is_api_compatible(handle->m_api_version, SCAP_MINIMUM_PROBE_API_VERSION))
{
snprintf(error, SCAP_LASTERR_SIZE, "Probe supports API version %lu.%lu.%lu, but running version needs %d.%d.%d",
leogr marked this conversation as resolved.
Show resolved Hide resolved
PPM_API_VERSION_MAJOR(handle->m_api_version),
PPM_API_VERSION_MINOR(handle->m_api_version),
PPM_API_VERSION_PATCH(handle->m_api_version),
PPM_API_CURRENT_VERSION_MAJOR,
PPM_API_CURRENT_VERSION_MINOR,
PPM_API_CURRENT_VERSION_PATCH);
*rc = SCAP_FAILURE;
scap_close(handle);
return NULL;
}

if(!scap_is_api_compatible(handle->m_schema_version, SCAP_MINIMUM_PROBE_SCHEMA_VERSION))
{
snprintf(error, SCAP_LASTERR_SIZE, "Probe supports schema version %lu.%lu.%lu, but running version needs %d.%d.%d",
PPM_API_VERSION_MAJOR(handle->m_schema_version),
PPM_API_VERSION_MINOR(handle->m_schema_version),
PPM_API_VERSION_PATCH(handle->m_schema_version),
PPM_SCHEMA_CURRENT_VERSION_MAJOR,
PPM_SCHEMA_CURRENT_VERSION_MINOR,
PPM_SCHEMA_CURRENT_VERSION_PATCH);
*rc = SCAP_FAILURE;
scap_close(handle);
return NULL;
}

for(j = 0; j < handle->m_ndevs; ++j)
{
//
Expand Down Expand Up @@ -2968,4 +3060,43 @@ int32_t scap_set_statsd_port(scap_t* const handle, const uint16_t port)

return SCAP_SUCCESS;
#endif
}
}

bool scap_is_api_compatible(unsigned long probe_api_version, unsigned long required_api_version)
{
unsigned long probe_major = PPM_API_VERSION_MAJOR(probe_api_version);
unsigned long probe_minor = PPM_API_VERSION_MINOR(probe_api_version);
unsigned long probe_patch = PPM_API_VERSION_PATCH(probe_api_version);
unsigned long required_major = PPM_API_VERSION_MAJOR(required_api_version);
unsigned long required_minor = PPM_API_VERSION_MINOR(required_api_version);
unsigned long required_patch = PPM_API_VERSION_PATCH(required_api_version);

if(probe_major != required_major)
{
// major numbers disagree
return false;
}

if(probe_minor < required_minor)
{
// probe's minor version is < ours
return false;
}
if(probe_minor == required_minor && probe_patch < required_patch)
{
// probe's minor versions match and patch level is < ours
return false;
}

return true;
}

uint64_t scap_get_probe_api_version(scap_t* handle)
leogr marked this conversation as resolved.
Show resolved Hide resolved
{
return handle->m_api_version;
}

uint64_t scap_get_probe_schema_version(scap_t* handle)
leogr marked this conversation as resolved.
Show resolved Hide resolved
{
return handle->m_schema_version;
}
Loading