[RFC PATCH 0/1] Landlock network PoC
Mickaël Salaün
mic at digikod.net
Thu Dec 30 23:26:42 UTC 2021
On 30/12/2021 02:43, Konstantin Meskhidze wrote:
> Hi Mickaël,
> Thanks for the quick reply.
> I apologise that I did not follow some rules here.
> I'm a newbie in the Linux community communication and probably missed
> some important points.
> Thank you for noticing about them - I will fix my misses.
No worries, these RFCs are here to improve the proposition but also to
learn.
>
> 12/29/2021 1:09 AM, Mickaël Salaün wrote:
>> Hi Konstantin,
>>
>> Please read the full how-to about sending patches:
>> https://www.kernel.org/doc/html/latest/process/submitting-patches.html
>>
>> There are at least these issues:
>> - no link to the previous version:
>> https://lore.kernel.org/linux-security-module/20211210072123.386713-1-konstantin.meskhidze@huawei.com/
>>
>> - no version: [RFC PATCH v2 0/1]
>> - even if there is only one patch, please make the cover letter a
>> separate email (i.e. git format-patch --cover-letter).
>
> I got it. My mistake.
> Anyway I can resend the patch ,if you would like, with all corrections.
No need to resend the same patch series, just follow these rules for the
next patch series.
>
>>
>> It seems that you missed some of my previous (inlined) comments, you
>> didn't reply to most of them:
>> https://lore.kernel.org/linux-security-module/b50ed53a-683e-77cf-9dc2-f4ae1b5fa0fd@digikod.net/
>>
>
> Sorry about that. I will take it into account.
> I will reply your previous comments.
>
>>
>> Did you test your changes before submitting this patch series?
>
> I tested it into QEMU environment. I wrote net_sandboxer.c
> file just for test insering network rules and bind() and connect()
> syscalls.
Ok, but I'm wondering about the content if this code and what you
actually tested with it. This is one reason why included standalone
tests are useful.
> Also I launched your sandboxer.c and it worked well.
> I'm going to provide seltests in another patch series.
Great!
>
>>
>> This series can only forbids TCP connects and binds to processes other
>> than the one creating the ruleset, which doesn't make sense here (as I
>> already explained). TCP ports are not checked.
>
> Yes. I provided just inserting network access rules into a prosess
> ruleset.For the port checking, I wanted to ask your opinion about how it
> would possible to insert port rule. Cause I have my view, that differs
> from your one.
>
>>
>> If you have any doubts about any of the comments, please rephrase,
>> challenge them and ask questions.
>>
>>
>> On 28/12/2021 12:52, Konstantin Meskhidze wrote:
>>> Hi, all!
>>> Here is another PoC patch for Landlock network confinement.
>>> Now 2 hooks are supported for TCP sockets:
>>> - hook_socket_bind()
>>> - hook_socket_connect()
>>>
>>> After architectuire has been more clear, there will be a patch with
>>> selftests.
>>>
>>> Please welcome with any comments and suggestions.
>>>
>>>
>>> Implementation related issues
>>> =============================
>>>
>>> 1. It was suggested by Mickaёl using new network rules
>>> attributes, like:
>>>
>>> struct landlock_net_service_attr {
>>> __u64 allowed_access; // LANDLOCK_NET_*_TCP
>>> __u16 port;
>>> } __attribute__((packed));
>>>
>>> I found that, if we want to support inserting port attributes,
>>> it's needed to add port member into struct landlock_rule:
>>>
>>> struct landlock_rule {
>>> ...
>>> struct landlock_object *object;
>>> /**
>>> * @num_layers: Number of entries in @layers.
>>> */
>>> u32 num_layers;
>>>
>>> u16 port;
>>
>> In this case the "object" is indeed defined by a port. You can then
>> create an union containing either a struct landlock_object pointer or
>> a raw value (here a u16 port). Of course, every code that use the
>> object field must be audited and updated accordingly. I think the
>> following update should be a good approach (with updated documentation):
>>
>> struct landlock_rule {
>> [...]
>> union {
>> struct landlock_object *ptr;
>> uintptr_t data;
>> } object;
>> [...]
>> };
>>
>> …and then a function helper to convert raw data to/from port.
>>
>> It would be a good idea to use a dedicated tree for objects identified
>> by (typed) data vs. pointer:
>>
>> struct landlock_ruleset {
>> struct rb_root root_inode; // i.e. the current "root" field.
>> struct rb_root root_net_port;
>> [...]
>> };
>>
>
> This approach makes sense. I did not think in this way. I was following
> the concept that every rule must be tied to a real kernel object. Using
> pseudo "object" defined by a port is a good idea. Thanks.
>
>>
>>> ...
>>> };
>>>
>>> In this case 2 functions landlock_insert_rule() and insert_rule()
>>> must be refactored;
>>>
>>> But, if struct landlock_layer be modified -
>>>
>>> struct landlock_layer {
>>> /**
>>> * @level: Position of this layer in the layer stack.
>>> */
>>> u16 level;
>>> /**
>>> * @access: Bitfield of allowed actions on the kernel object.
>>> They are
>>> * relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
>>> */
>>> u16 access;
>>>
>>> u16 port;
>>
>> No, struct landlock_layer doesn't need to be modified. This struct is
>> independent from the type of object. I talked about that in the
>> previous series.
>
> I got it. Thanks for the comment.
>
>>
>>> };
>>> so, just one landlock_insert_rule() must be slightly refactored.
>>> Also many new attributes could be easily supported in future versions.
>>>
>>> 2. access_masks[] member of struct landlock_ruleset was modified
>>> to support multiple rule type masks.
>>> I suggest using 2D array semantic for convenient usage:
>>> access_masks[rule_type][layer_level]
>>>
>>> But its also possible to use 1D array with modulo arithmetic:
>>> access_masks[rule_type % layer_level]
>>
>> What about my previous suggestion to use a (well defined) upper bit to
>> identify the type of access?
>>
>
> Maybe I missed your point here. But I can't see how to identify
> different rule types here. If there just one ruleset created, so there
> is just one access_mask[0] in the ruleset. Its either can be used for
> filesystem mask or for network one. To support both rule type we need to
> use at least access_mask[] array with size of 2.
> Also using upper bit to identify access rule type will narrow down
> possible access ammount in future version.
> Anyway please corrent me if I'm wrong here.
We should avoid using multi-dimentional arrays because it makes the code
more complex (e.g. when allocating memory).
The idea is to split the access masks' u16 to the actual access masks
and a bit to identify the type of access. Because there is no more than
13 access rights for now (per access type, i.e. for the file system),
the 3 upper bits are unused. A simple bit mask (and a shift) can get
these 3 upper bits or the 13 lower bits. If/when new access bits will be
used, we'll just need to upgrade the u16 to a u32, but the logic will be
the same and the userspace ABI unchanged.
[...]
>>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>>> index ec72b9262bf3..a335c475965c 100644
>>> --- a/security/landlock/ruleset.c
>>> +++ b/security/landlock/ruleset.c
>>> @@ -27,9 +27,24 @@
>>> static struct landlock_ruleset *create_ruleset(const u32 num_layers)
>>> {
>>> struct landlock_ruleset *new_ruleset;
>>> + u16 row, col, rules_types_num;
>>> +
>>> + new_ruleset = kzalloc(sizeof *new_ruleset +
>>> + sizeof *(new_ruleset->access_masks),
>>
>> sizeof(access_masks) is 0.
>
> Actually sizeof *(new_ruleset->access_masks) is 8.
> It's a 64 bit pointer to u16 array[]. I checked this
> 2D FAM array implementation in a standalone test.
Yes, this gives the size of the pointed element, but I wanted to point
out that access_masks doesn't have a size (actually a sizeof() call on
it would failed). This kzalloc() only allocates one element in the
array. What happen when there is more than one layer?
>
>>
>>> + GFP_KERNEL_ACCOUNT);
>>> +
>>> + rules_types_num = LANDLOCK_RULE_TYPE_NUM;
>>> + /* Initializes access_mask array for multiple rule types.
>>> + * Double array semantic is used convenience:
>>> access_mask[rule_type][num_layer].
>>> + */
>>> + for (row = 0; row < rules_types_num; row++) {
>>> + new_ruleset->access_masks[row] = kzalloc(sizeof
>>> + *(new_ruleset->access_masks[row]),
>>> + GFP_KERNEL_ACCOUNT);
>>> + for (col = 0; col < num_layers; col++)
>>> + new_ruleset->access_masks[row][col] = 0;
>>> + }
>>
>> This code may segfault. I guess it wasn't tested. Please enable most
>> test/check features as I suggested in the previous series.
>
> I compiled the kernel 5.13 with this patch and tested it in QEMU. No
> crashes. I tested your sandboxer.c and it works without segfaults.
> But I will provide seltests in future patch.
Did you run the current selftests?
fakeroot make -C tools/testing/selftests TARGETS=landlock gen_tar
tar -xf
tools/testing/selftests/kselftest_install/kselftest-packages/kselftest.tar.gz
# as root in a VM with Landlock enabled:
./run_kselftest.sh
BTW, you should test with the latest kernel (i.e. latest Linus's tag).
>
>
>>
>>>
>>> - new_ruleset = kzalloc(struct_size(new_ruleset, fs_access_masks,
>>> - num_layers), GFP_KERNEL_ACCOUNT);
>>
>> What about my comment in the previous series?
>
> As I replied above.
> Maybe I missed your point here. But I can't see how to identify
> different rule types here. If there just one ruleset created, so there
> is just one access_mask[0] in the ruleset. Its either can be used for
> filesystem mask or for network one. To support both rule type we need to
> use at least access_mask[] array with size of 2.
> Also using upper bit to identify access rule type will narrow down
> possible access ammount in future version.
> I suggest using 2D array or 1D array with module arithmetic to
> support different rule type masks.
> Anyway please corrent me if I'm wrong here.
See my above comment.
[...]
More information about the Linux-security-module-archive
mailing list