File: usergroup_retain_cap.hpp

package info (click to toggle)
openvpn3-client 24.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 19,384 kB
  • sloc: cpp: 180,128; python: 11,591; ansic: 1,878; sh: 1,767; java: 402; lisp: 81; makefile: 44
file content (204 lines) | stat: -rw-r--r-- 6,058 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2022 OpenVPN Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

// Drop root privileges but retain one or more Linux capabilities

#pragma once

#include <sys/capability.h>

#include <string>
#include <vector>
#include <utility>
#include <initializer_list>

#include <openvpn/common/numeric_cast.hpp>
#include <openvpn/common/usergroup.hpp>

#ifndef OPENVPN_PLATFORM_LINUX
#error SetUserGroupRetainCap requires Linux
#endif

namespace openvpn {

class SetUserGroupRetainCap : public SetUserGroup
{
  public:
    SetUserGroupRetainCap(const std::string &user,
                          const std::string &group,
                          const bool strict,
                          std::initializer_list<cap_value_t> retain_caps_arg)
        : SetUserGroup(user, group, strict),
          retain_caps(retain_caps_arg)
    {
        grab_root();
    }

    SetUserGroupRetainCap(const char *user,
                          const char *group,
                          const bool strict,
                          std::initializer_list<cap_value_t> retain_caps_arg)
        : SetUserGroup(user, group, strict),
          retain_caps(retain_caps_arg)
    {
        grab_root();
    }

    // call first in all threads before user/group downgrade
    virtual void pre_thread() const override
    {
        if (!pw)
            return;

        // create a capabilities object
        Capabilities cap("pre_thread");

        // set retained capabilities + setuid/setgid
        cap.set_flag_with_setuid_setgid(retain_caps);

        // commit it to kernel
        cap.set_proc();

        // retain the capabilities across identity change
        if (::prctl(PR_SET_KEEPCAPS, 1L))
        {
            const int eno = errno;
            OPENVPN_THROW(user_group_err, "SetUserGroupRetainCap prctl PR_SET_KEEPCAPS fail: " << strerror_str(eno));
        }
    }

    // call once after pre_thread() called in each thread
    virtual void activate() const override
    {
        if (!pw)
        {
            SetUserGroup::activate();
            return;
        }

        // set GID/Groups
        do_setgid_setgroups();

        // drop extra privileges (aside from capabilities)
        if (::setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
        {
            const int eno = errno;
            OPENVPN_THROW(user_group_err, "SetUserGroupRetainCap setresuid user fail: " << strerror_str(eno));
        }

        // retain core dumps after UID/GID downgrade
        retain_core_dumps();

        // logging
        {
            Capabilities cap("logging");
            cap.set_flag(retain_caps);
            OPENVPN_LOG("UID [" << cap.to_string() << "] set to '" << user_name << '\'');
        }
    }

    // call in all threads after activate()
    virtual void post_thread() const override
    {
        if (!pw)
            return;

        // create a capabilities object
        Capabilities cap("post_thread");

        // set retained capabilities
        cap.set_flag(retain_caps);

        // commit it to kernel
        cap.set_proc();
    }

  private:
    class Capabilities
    {
      public:
        Capabilities(std::string title_arg)
            : capabilities(::cap_init()),
              title(std::move(title_arg))
        {
        }

        ~Capabilities()
        {
            ::cap_free(capabilities);
        }

        void set_flag(const std::vector<cap_value_t> &caps)
        {
            if (::cap_set_flag(capabilities, CAP_PERMITTED, numeric_cast<int>(caps.size()), caps.data(), CAP_SET)
                || ::cap_set_flag(capabilities, CAP_EFFECTIVE, numeric_cast<int>(caps.size()), caps.data(), CAP_SET))
            {
                const int eno = errno;
                OPENVPN_THROW(user_group_err, "SetUserGroupRetainCap::Capabilities: cap_set_flag " << title << " fail: " << strerror_str(eno));
            }
        }

        void set_flag_with_setuid_setgid(std::vector<cap_value_t> caps)
        {
            caps.push_back(CAP_SETUID);
            caps.push_back(CAP_SETGID);
            set_flag(caps);
        }

        void set_proc()
        {
            if (::cap_set_proc(capabilities))
            {
                const int eno = errno;
                OPENVPN_THROW(user_group_err, "SetUserGroupRetainCap::Capabilities: cap_set_proc " << title << " fail: " << strerror_str(eno));
            }
        }

        std::string to_string() const
        {
            char *txt = ::cap_to_text(capabilities, nullptr);
            std::string ret(txt);
            ::cap_free(txt);
            return ret;
        }

      private:
        Capabilities(const Capabilities &) = delete;
        Capabilities &operator=(const Capabilities &) = delete;

        const cap_t capabilities;
        const std::string title;
    };

    void grab_root()
    {
        // get full root privileges
        if (::setresuid(0, 0, 0))
        {
            const int eno = errno;
            OPENVPN_THROW(user_group_err, "SetUserGroupRetainCap setresuid root fail: " << strerror_str(eno));
        }
    }

    const std::vector<cap_value_t> retain_caps;
};

} // namespace openvpn