Re: View on pledge promise dynamic string creation

Thomas Kupper <[email protected]> Fri, 10 Jul 2026 07:19:53 +0100
Newsgroups gmane.os.openbsd.misc
Message-ID <[email protected]>
Claudio Jeker wrote on 09.07.2026 19:47:
> On Wed, Jul 08, 2026 at 08:47:34PM +0200, Claudio Jeker wrote:
>> On Wed, Jul 08, 2026 at 07:43:44AM +0100, Thomas Kupper wrote:
>>> Hi,
>>>
>>> I was wondering what are the views on having the pledge(2) promise assembled
>>> (in a 3rd party app). Scanning through base and ports, I could only find
>>> pledge(2) promises as constant string argument. I did assume that it's the
>>> safest way to make sure that the promise can't be fiddled with.
>>>
>>> Since the app (Chrony) was not designed with pledge in mind, if became more
>>> readable to assemble the promise string like:
>>>
>>> (https://gitlab.com/chrony/chrony/-/commit/0ea7607358cc)
>>> --- upstream commit 0ea76073 ---
>>>
>>>    if (snprintf(promises, sizeof (promises), "stdio%s%s%s%s",
>>>                 needs_main_misc ? " rpath wpath cpath inet unix dns" : "",
>>>                 needs_recvfd ? " recvfd" : "",
>>>                 needs_sendfd ? " sendfd" : "",
>>>                 needs_settime ? " settime" : "") >= sizeof (promises))
>>>      assert(0);
>>>
>>>    DEBUG_LOG("Pledging: %s", promises);
>>>
>>>    if (pledge(promises, NULL) < 0)
>>>      LOG_FATAL("pledge() failed");
>>>
>>> ---
>>>
>>> Originally I stuck to the constant strings and Miroslav optimized it after
>>> another change would have resulted in more complex if/else constructs.
>>>
>>
>> We use static strings because they are easier to review.
>> Also the strings all use the same order to again help review of pledge
>> usage. Adding new capabilities to a pledge promise requires a review of
>> all current usage of that promise to understand the impact. Having all
>> pledge calls be the same style makes this a lot easier.
>>
>> It is much harder to argue about your pledge above since it is unclear what
>> will end up in promises without code inspection. In this case it may not
>> matter since chrony is not part of base and ports pledge usage is normally
>> not checked in the above case.
>>
>> Btw. assert(0) as a way to fail after snprintf() failure is special. If
>> compiled with -DNODEBUG the error is just ignore.
> 
> As small follow-up. It is perfectly fine to call pledge multiple times and
> refine the promises over and over again.
> 
> If you look at e.g. usr.bin/nc/netcat.c you can see that the program uses
> a few check to get a initial starting promise but then later on more
> pledge calls are made especially once the the code enters the actuall
> process loop. There is no need to build up the perfect string in a single
> place, in some cases it may be easier to adjust the pledge at a few key
> places.

I understand and Shaun Ren (the original Author of the OpenBSD patch) 
did some #def-ing to add pledge and with that approach you can place it 
where necessary.

But Chrony has only some entry points for defining OS specific 
behavior/functions, all of them in sys_openbsd.[ch]. For example there 
is one for dropping privileges and another for setting system call 
filters. And I did stick to these for upstream OpenBSD support in 
Chrony. And sthen (rightfully) suggested to get OpenBSD support 
upstream-ed instead of a bigger patch for OpenBSD ports.

Similar, I don't now how to implement unveil(2) while adhere to the rules.