Reimplement on top of libhbsdcontrol v2

This commit is contained in:
0x1eef 2024-03-20 16:25:20 -03:00
parent 6ad5bca145
commit 3528765df4
22 changed files with 256 additions and 211 deletions

11
.clang-format Normal file
View file

@ -0,0 +1,11 @@
BasedOnStyle: LLVM
IndentWidth: 2
SortIncludes: false
UseTab: Never
BreakBeforeBraces: Allman
AllowShortFunctionsOnASingleLine: Inline
AlwaysBreakAfterDefinitionReturnType: TopLevel
BreakBeforeBinaryOperators: All
BinPackArguments: false
AlignConsecutiveAssignments: true
AlwaysBreakAfterReturnType: None

View file

@ -4,8 +4,10 @@ Rake::ExtensionTask.new("hbsdctl.rb")
task default: %w[clobber compile test] task default: %w[clobber compile test]
desc "Run C linter" desc "Run C linter"
task :styleguide do namespace :clang do
sh "uncrustify -c .styleguide.cfg --no-backup ext/hbsdctl.rb/*.c" task :format do
sh "clang-format -style=file:.clang-format -i ext/hbsdctl.rb/*.c"
end
end end
namespace :test do namespace :test do

68
ext/hbsdctl.rb/context.c Normal file
View file

@ -0,0 +1,68 @@
#include <ruby.h>
#include <libhbsdcontrol.h>
#include "context.h"
#include "glue.h"
static int FLAGS = HBSDCTRL_FEATURE_STATE_FLAG_NONE;
static const char *NAMESPACE = LIBHBSDCONTROL_DEFAULT_NAMESPACE;
static void bsdcontrol_context_free(struct bsdcontrol_ctx_t *);
VALUE
bsdcontrol_context_alloc(VALUE klass)
{
hbsdctrl_ctx_t *ctx;
struct bsdcontrol_ctx_t *rbctx;
ctx = hbsdctrl_ctx_new(FLAGS, NAMESPACE);
rbctx = calloc(1, sizeof(struct bsdcontrol_ctx_t));
if (ctx == NULL || rbctx == NULL)
{
rb_raise(rb_eRuntimeError, "...");
}
rbctx->ctx = ctx;
return Data_Wrap_Struct(klass, NULL, bsdcontrol_context_free, rbctx);
}
static void
bsdcontrol_context_free(struct bsdcontrol_ctx_t *rbctx)
{
hbsdctrl_ctx_free(&rbctx->ctx);
free(rbctx);
}
/*
* BSD::Control::Context#available_features
* BSD::Control.available_features
* BSD::Control::Feature.available
*/
VALUE
bsdcontrol_context_available_features(VALUE self)
{
VALUE rb_mBSD = rb_const_get(rb_cObject, rb_intern("BSD")),
rb_mControl = rb_const_get(rb_mBSD, rb_intern("Control")),
rb_cFeature = rb_const_get(rb_mControl, rb_intern("Feature")),
feature = 0, features = rb_ary_new();
hbsdctrl_ctx_t *ctx;
char **name;
ctx = bsdcontrol_unwrap(self);
name = hbsdctrl_ctx_all_feature_names(ctx);
while (*name != NULL)
{
feature = rb_funcall(
rb_cFeature, rb_intern("new"), 2, rb_str_new2(*name), self);
rb_ary_push(features, feature);
name++;
}
return features;
}
/*
* BSD::Control::Context#library_version
* BSD::Control.library_version
*/
VALUE
bsdcontrol_context_library_version(VALUE self)
{
hbsdctrl_ctx_t *ctx;
ctx = bsdcontrol_unwrap(self);
return ULONG2NUM(ctx->hc_version);
}

10
ext/hbsdctl.rb/context.h Normal file
View file

@ -0,0 +1,10 @@
#include <ruby.h>
#include <libhbsdcontrol.h>
struct bsdcontrol_ctx_t {
hbsdctrl_ctx_t *ctx;
};
VALUE bsdcontrol_context_alloc(VALUE klass);
VALUE bsdcontrol_context_library_version(VALUE self);
VALUE bsdcontrol_context_available_features(VALUE self);

View file

@ -1,34 +1,67 @@
#include <ruby.h>
#include <libhbsdcontrol.h> #include <libhbsdcontrol.h>
#include <fcntl.h>
#include "feature.h"
#include "context.h"
#include "glue.h"
#include <errno.h> #include <errno.h>
#include "include/feature.h"
static VALUE __set(VALUE, VALUE, VALUE);
/** /*
* BSD::Control::Feature#set! * BSD::Control::Feature#status
**/ */
VALUE VALUE
feature_set(VALUE self, VALUE rb_path, VALUE rb_state) bsdcontrol_feature_status(VALUE self, VALUE path)
{ {
Check_Type(rb_path, T_STRING); int fd;
Check_Type(rb_state, T_FIXNUM); VALUE rbcontext;
VALUE rb_feature = rb_funcall(self, rb_intern("name"), 0); hbsdctrl_feature_t *feature;
Check_Type(rb_feature, T_STRING); hbsdctrl_feature_state_t state;
return (__set(rb_path, rb_feature, rb_state)); hbsdctrl_ctx_t *ctx;
} rbcontext = rb_funcall(self, rb_intern("context"), 0);
fd = bsdcontrol_open(path);
ctx = bsdcontrol_unwrap(rbcontext);
static VALUE feature = bsdcontrol_find_feature(ctx, self);
__set(VALUE rb_path, VALUE rb_feature, VALUE rb_state) errno = 0;
{ if (feature->hf_get(ctx, feature, &fd, &state) == RES_FAIL)
errno = 0; {
int result = hbsdcontrol_set_feature_state( close(fd);
RSTRING_PTR(rb_path), errno == 0 ? rb_raise(rb_eRuntimeError, "hf_get")
RSTRING_PTR(rb_feature), : rb_syserr_fail(errno, "hf_get");
NUM2INT(rb_state) }
); else
if (result == 0) { {
return (Qtrue); const char *str;
} else { close(fd);
rb_syserr_fail(errno, "hbsdcontrol_set_feature_state"); str = hbsdctrl_feature_state_to_string(&state);
return ID2SYM(rb_intern(str));
}
}
/*
* BSD::Control::Feature#set!
*/
VALUE
bsdcontrol_feature_set(VALUE self, VALUE path, VALUE rbstate)
{
int fd;
VALUE rbcontext;
hbsdctrl_feature_t *feature;
hbsdctrl_ctx_t *ctx;
int state;
rbcontext = rb_funcall(self, rb_intern("context"), 0);
fd = bsdcontrol_open(path);
ctx = bsdcontrol_unwrap(rbcontext);
feature = bsdcontrol_find_feature(ctx, self);
state = NUM2INT(rbstate);
errno = 0;
if (feature->hf_apply(ctx, feature, &fd, &state) == RES_FAIL)
{
close(fd);
errno == 0 ? rb_raise(rb_eRuntimeError, "hf_apply")
: rb_syserr_fail(errno, "hf_apply");
}
else
{
return Qtrue;
} }
} }

3
ext/hbsdctl.rb/feature.h Normal file
View file

@ -0,0 +1,3 @@
#include <ruby.h>
VALUE bsdcontrol_feature_status(VALUE, VALUE);
VALUE bsdcontrol_feature_set(VALUE,VALUE,VALUE);

View file

@ -1,133 +0,0 @@
#include <libhbsdcontrol.h>
#include <ruby.h>
#include <sys/extattr.h>
#include <libutil.h>
#include <errno.h>
#include "include/ffi.h"
static struct Options __options_init(VALUE, VALUE);
struct Options {
char *path;
char *enable_flag;
char *disable_flag;
};
/**
* BSD::Control::FFI.available_features
**/
VALUE
ffi_available_features(VALUE self)
{
const struct pax_feature_entry *entry = &pax_features[0];
VALUE rb_mBSD = rb_const_get(rb_cObject, rb_intern("BSD")),
rb_mControl = rb_const_get(rb_mBSD, rb_intern("Control")),
rb_cFeature = rb_const_get(rb_mControl, rb_intern("Feature")),
features = rb_ary_new(),
feature = 0;
while (entry->feature != NULL)
{
feature = rb_funcall(
rb_cFeature,
rb_intern("new"),
3,
rb_str_new2(entry->feature),
rb_str_new2(entry->extattr[1]),
rb_str_new2(entry->extattr[0])
);
rb_ary_push(features, feature);
entry++;
}
return (features);
}
/**
* BSD::Control::FFI.sysdef!
**/
VALUE
ffi_sysdef(VALUE self, VALUE rb_feature, VALUE rb_path)
{
struct Options options;
int result;
errno = 0;
options = __options_init(rb_feature, rb_path);
result = hbsdcontrol_extattr_rm_attr(options.path, options.disable_flag) == 0 &&
hbsdcontrol_extattr_rm_attr(options.path, options.enable_flag) == 0;
if (result) {
return (Qtrue);
} else {
rb_syserr_fail(errno, "hbsdcontrol_extattr_rm_attr");
}
}
/**
* BSD::Control::FFI.status
**/
VALUE
ffi_status(VALUE self, VALUE rb_feature, VALUE rb_path)
{
struct Options options;
char enable_data[2], disable_data[2];
int ns;
errno = 0;
options = __options_init(rb_feature, rb_path);
if (extattr_string_to_namespace("system", &ns) == -1) {
rb_syserr_fail(errno, "extattr_string_to_namespace");
}
if (
extattr_get_file(
options.path, ns,
options.enable_flag, &enable_data,
2) == -1) {
rb_syserr_fail(errno, "extattr_get_file");
}
if (
extattr_get_file(
options.path, ns,
options.disable_flag, &disable_data,
2) == -1) {
rb_syserr_fail(errno, "extattr_get_file");
}
if (strncmp(enable_data, disable_data, 1) == 0) {
return (ID2SYM(rb_intern("conflict")));
} else if (strncmp(enable_data, "1", 1) == 0) {
return (ID2SYM(rb_intern("enabled")));
} else {
return (ID2SYM(rb_intern("disabled")));
}
}
/**
* BSD::Control::FFI.library_version
**/
VALUE
ffi_library_version(VALUE self)
{
const char *ver;
ver = hbsdcontrol_get_version();
return (rb_str_new2(ver));
}
static
struct Options
__options_init(VALUE rb_feature, VALUE rb_path)
{
VALUE rb_enable_flag, rb_disable_flag;
struct Options options;
rb_enable_flag = rb_funcall(rb_feature, rb_intern("enable"), 0);
rb_disable_flag = rb_funcall(rb_feature, rb_intern("disable"), 0);
Check_Type(rb_enable_flag, T_STRING);
Check_Type(rb_disable_flag, T_STRING);
Check_Type(rb_path, T_STRING);
options.path = RSTRING_PTR(rb_path);
options.enable_flag = RSTRING_PTR(rb_enable_flag);
options.disable_flag = RSTRING_PTR(rb_disable_flag);
return (options);
}

34
ext/hbsdctl.rb/glue.c Normal file
View file

@ -0,0 +1,34 @@
#include <ruby.h>
#include <libhbsdcontrol.h>
#include <fcntl.h>
#include "glue.h"
#include "context.h"
int
bsdcontrol_open(VALUE path)
{
int fd;
fd = open(RSTRING_PTR(path), O_PATH);
if (fd == -1)
{
rb_syserr_fail(errno, "open");
}
return fd;
}
hbsdctrl_ctx_t *
bsdcontrol_unwrap(VALUE rbcontext)
{
struct bsdcontrol_ctx_t *rbctx;
Data_Get_Struct(rbcontext, struct bsdcontrol_ctx_t, rbctx);
return rbctx->ctx;
}
hbsdctrl_feature_t *
bsdcontrol_find_feature(hbsdctrl_ctx_t *ctx, VALUE rbfeature)
{
VALUE name;
name = rb_funcall(rbfeature, rb_intern("name"), 0);
Check_Type(name, T_STRING);
return hbsdctrl_ctx_find_feature_by_name(ctx, RSTRING_PTR(name));
}

7
ext/hbsdctl.rb/glue.h Normal file
View file

@ -0,0 +1,7 @@
#pragma once
#include <ruby.h>
#include <libhbsdcontrol.h>
int bsdcontrol_open(VALUE);
hbsdctrl_ctx_t* bsdcontrol_unwrap(VALUE);
hbsdctrl_feature_t* bsdcontrol_find_feature(hbsdctrl_ctx_t*, VALUE);

View file

@ -1,20 +1,22 @@
#include <ruby.h> #include <ruby.h>
#include "include/ffi.h" #include <ruby.h>
#include "include/feature.h" #include "context.h"
#include "feature.h"
void void
Init_hbsdctl(void) Init_hbsdctl(void)
{ {
VALUE rb_mBSD = rb_const_get(rb_cObject, rb_intern("BSD")), VALUE rb_mBSD = rb_const_get(rb_cObject, rb_intern("BSD")),
rb_mControl = rb_const_get(rb_mBSD, rb_intern("Control")), rb_mControl = rb_const_get(rb_mBSD, rb_intern("Control")),
rb_cFeature = rb_const_get(rb_mControl, rb_intern("Feature")), rb_cFeature = rb_const_get(rb_mControl, rb_intern("Feature")),
rb_mFFI = rb_const_get(rb_mControl, rb_intern("FFI")); rb_cContext = rb_const_get(rb_mControl, rb_intern("Context"));
rb_define_alloc_func(rb_cContext, bsdcontrol_context_alloc);
rb_define_const(rb_mControl, "Disable", INT2NUM(0)); rb_define_method(
rb_define_const(rb_mControl, "Enable", INT2NUM(1)); rb_cContext, "library_version", bsdcontrol_context_library_version, 0);
rb_define_singleton_method(rb_mFFI, "available_features", ffi_available_features, 0); rb_define_method(rb_cContext,
rb_define_singleton_method(rb_mFFI, "library_version", ffi_library_version, 0); "available_features",
rb_define_singleton_method(rb_mFFI, "sysdef!", ffi_sysdef, 2); bsdcontrol_context_available_features,
rb_define_singleton_method(rb_mFFI, "status", ffi_status, 2); 0);
rb_define_private_method(rb_cFeature, "set!", feature_set, 2); rb_define_method(rb_cFeature, "status", bsdcontrol_feature_status, 1);
rb_define_private_method(rb_cFeature, "set!", bsdcontrol_feature_set, 2);
} }

View file

@ -1,2 +0,0 @@
#include <ruby.h>
VALUE feature_set(VALUE, VALUE, VALUE);

View file

@ -1,5 +0,0 @@
#include <ruby.h>
VALUE ffi_library_version(VALUE);
VALUE ffi_available_features(VALUE);
VALUE ffi_status(VALUE, VALUE, VALUE);
VALUE ffi_sysdef(VALUE, VALUE, VALUE);

View file

@ -1,19 +1,27 @@
module BSD::Control module BSD::Control
require_relative "control/context"
require_relative "control/feature" require_relative "control/feature"
Error = Class.new(RuntimeError) Error = Class.new(RuntimeError)
##
# @return [BSD::Control::Context]
# Returns an instance of {BSD::Control::Context BSD::Control::Context}.
def self.context
@context ||= BSD::Control::Context.new
end
## ##
# @return [String] # @return [String]
# Returns the version of libhbsdcontrol. # Returns the version of libhbsdcontrol.
def self.library_version def self.library_version
FFI.library_version context.library_version
end end
## ##
# @return [Array<BSD::Control::Feature>] # @return [Array<BSD::Control::Feature>]
# Returns an array of available features. # Returns an array of available features.
def self.available_features def self.available_features
Feature.available context.available_features
end end
## ##

View file

@ -0,0 +1,7 @@
module BSD::Control
##
# The {BSD::Control::Context BSD::Control::Context} class encapsulates
# and persists a clang data structure (hbsdctrl_ctx_t).
class Context
end
end

View file

@ -1,10 +1,10 @@
module BSD::Control module BSD::Control
class Feature < Struct.new(:name, :enable, :disable) class Feature < Struct.new(:name, :context)
## ##
# @return [Array<BSD::Control::Feature>] # @return [Array<BSD::Control::Feature>]
# Returns an array of available features. # Returns an array of available features.
def self.available def self.available
BSD::Control::FFI.available_features BSD::Control.available_features
end end
## ##
@ -22,7 +22,7 @@ module BSD::Control
# @return [Boolean] # @return [Boolean]
# Returns true on success. # Returns true on success.
def enable!(path) def enable!(path)
set!(path, BSD::Control::Enable) set!(path, 1)
end end
## ##
@ -37,7 +37,7 @@ module BSD::Control
# @return [Boolean] # @return [Boolean]
# Returns true on success. # Returns true on success.
def disable!(path) def disable!(path)
set!(path, BSD::Control::Disable) set!(path, 0)
end end
## ##

View file

@ -4,9 +4,9 @@ module BSD::Control
require 'fileutils' require 'fileutils'
include FileUtils include FileUtils
def test_disable_mprotect_nonexistent_file def test_disable_pageexec_nonexistent_file
assert_raises(Errno::ENOENT) do assert_raises(Errno::ENOENT) do
BSD::Control.feature(:mprotect).disable!(file) BSD::Control.feature(:pageexec).disable!(file)
end end
end end

View file

@ -4,26 +4,26 @@ module BSD::Control
require 'fileutils' require 'fileutils'
include FileUtils include FileUtils
def test_enable_mprotect def test_enable_pageexec
touch(file) touch(file)
assert BSD::Control.feature(:mprotect).enable!(file), assert BSD::Control.feature(:pageexec).enable!(file),
"The enable! method should have returned true" "The enable! method should have returned true"
ensure ensure
rm(file) rm(file)
end end
def test_enable_mprotect_zero_permissions def test_enable_pageexec_zero_permissions
touch(file) touch(file)
chmod(0, file) chmod(0, file)
assert BSD::Control.feature(:mprotect).enable!(file), assert BSD::Control.feature(:pageexec).enable!(file),
"The enable! method should have returned true" "The enable! method should have returned true"
ensure ensure
rm(file) rm(file)
end end
def test_enable_mprotect_nonexistent_file def test_enable_pageexec_nonexistent_file
assert_raises(Errno::ENOENT) do assert_raises(Errno::ENOENT) do
BSD::Control.feature(:mprotect).enable!(file) BSD::Control.feature(:pageexec).enable!(file)
end end
end end

View file

@ -4,29 +4,29 @@ module BSD::Control
require 'fileutils' require 'fileutils'
include FileUtils include FileUtils
def test_mprotect_sysdef_status def test_pageexec_sysdef_status
touch(file) touch(file)
assert_equal :sysdef, assert_equal :sysdef,
BSD::Control.feature(:mprotect).status(file) BSD::Control.feature(:pageexec).status(file)
ensure ensure
rm(file) rm(file)
end end
def test_mprotect_enabled_status def test_pageexec_enabled_status
touch(file) touch(file)
BSD::Control.feature(:mprotect).enable!(file) BSD::Control.feature(:pageexec).enable!(file)
assert_equal :enabled, assert_equal :enabled,
BSD::Control.feature(:mprotect).status(file) BSD::Control.feature(:pageexec).status(file)
ensure ensure
rm(file) rm(file)
end end
def test_mprotect_disabled_status def test_pageexec_disabled_status
touch(file) touch(file)
BSD::Control.feature(:mprotect).disable!(file) BSD::Control.feature(:pageexec).disable!(file)
assert_equal :disabled, assert_equal :disabled,
BSD::Control.feature(:mprotect).status(file) BSD::Control.feature(:pageexec).status(file)
ensure ensure
rm(file) rm(file)
end end

View file

@ -4,9 +4,9 @@ module BSD::Control
require 'fileutils' require 'fileutils'
include FileUtils include FileUtils
def test_disable_mprotect_nonexistent_file def test_disable_pageexec_nonexistent_file
assert_raises(Errno::ENOENT) do assert_raises(Errno::ENOENT) do
BSD::Control.feature(:mprotect).disable!(file) BSD::Control.feature(:pageexec).disable!(file)
end end
end end

View file

@ -7,7 +7,7 @@ module BSD::Control
def test_enable_feature_lacks_privileges def test_enable_feature_lacks_privileges
touch(file) touch(file)
assert_raises(Errno::EPERM) do assert_raises(Errno::EPERM) do
BSD::Control.feature(:mprotect).enable!(file) BSD::Control.feature(:pageexec).enable!(file)
end end
ensure ensure
rm(file) rm(file)

View file

@ -1,9 +1,9 @@
require_relative "../setup" require_relative "../setup"
module BSD::Control module BSD::Control
class FeatureTest < Test::Unit::TestCase class FeatureTest < Test::Unit::TestCase
def test_mprotect_feature def test_pageexec_feature
assert_instance_of BSD::Control::Feature, assert_instance_of BSD::Control::Feature,
BSD::Control.feature(:mprotect) BSD::Control.feature(:pageexec)
end end
def test_nonexistent_feature def test_nonexistent_feature

View file

@ -7,7 +7,7 @@ module BSD::Control
def test_sysdef!_lacks_privileges def test_sysdef!_lacks_privileges
touch(file) touch(file)
assert_raises(Errno::EPERM) do assert_raises(Errno::EPERM) do
BSD::Control.feature(:mprotect).sysdef!(file) BSD::Control.feature(:pageexec).sysdef!(file)
end end
ensure ensure
rm(file) rm(file)