Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
R
relibc
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container Registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Peter Limkilde Svendsen
relibc
Commits
37cc4e53
Verified
Commit
37cc4e53
authored
2 years ago
by
Jacob Lorentzon
Browse files
Options
Downloads
Patches
Plain Diff
WIP: Add setuid/setgid support.
parent
c95d276a
No related branches found
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/platform/redox/mod.rs
+117
-73
117 additions, 73 deletions
src/platform/redox/mod.rs
with
117 additions
and
73 deletions
src/platform/redox/mod.rs
+
117
−
73
View file @
37cc4e53
...
...
@@ -14,10 +14,11 @@ use crate::{
dirent
::
dirent
,
errno
::{
EINVAL
,
EIO
,
ENOMEM
,
EPERM
,
ERANGE
},
fcntl
,
string
::
strlen
,
sys_mman
::{
MAP_ANONYMOUS
,
PROT_READ
,
PROT_WRITE
},
sys_random
,
sys_resource
::{
rlimit
,
RLIM_INFINITY
},
sys_stat
::
stat
,
sys_stat
::
{
stat
,
S_ISGID
,
S_ISUID
},
sys_statvfs
::
statvfs
,
sys_time
::{
timeval
,
timezone
},
sys_utsname
::{
utsname
,
UTSLENGTH
},
...
...
@@ -65,6 +66,14 @@ pub fn e(sys: Result<usize>) -> usize {
}
}
}
fn
flatten_with_nul
<
T
>
(
iter
:
impl
IntoIterator
<
Item
=
T
>
)
->
Box
<
[
u8
]
>
where
T
:
AsRef
<
[
u8
]
>
{
let
mut
vec
=
Vec
::
new
();
for
item
in
iter
{
vec
.extend
(
item
.as_ref
());
vec
.push
(
b'\0'
);
}
vec
.into_boxed_slice
()
}
pub
struct
Sys
;
...
...
@@ -215,81 +224,87 @@ impl Pal for Sys {
};
let
fd
=
*
file
as
usize
;
// With execve now being implemented in userspace, we need to check ourselves that this
// file is actually executable. While checking for read permission is unnecessary as the
// scheme will not allow us to read otherwise, the execute bit is completely unenforced. We
// have the permission to mmap executable memory and fill it with the program even if it is
// unset, so the best we can do is check that nothing is executed by accident.
//
// TODO: At some point we might have capabilities limiting the ability to allocate
// executable memory, and in that case we might use the `escalate:` scheme as we already do
// when the binary needs setuid/setgid.
let
mut
stat
=
redox_stat
::
default
();
if
e
(
syscall
::
fstat
(
fd
,
&
mut
stat
))
==
!
0
{
return
-
1
;
}
let
uid
=
e
(
syscall
::
getuid
());
if
uid
==
!
0
{
return
-
1
;
}
let
gid
=
e
(
syscall
::
getuid
());
if
gid
==
!
0
{
return
-
1
;
}
let
mode
=
if
uid
==
stat
.st_uid
as
usize
{
(
stat
.st_mode
>>
3
*
2
)
&
0o7
}
else
if
gid
==
stat
.st_gid
as
usize
{
(
stat
.st_mode
>>
3
*
1
)
&
0o7
}
else
{
stat
.st_mode
&
0o7
};
if
mode
&
0o1
==
0o0
{
errno
=
EPERM
;
return
-
1
;
}
let
wants_setugid
=
stat
.st_mode
&
((
S_ISUID
|
S_ISGID
)
as
u16
)
!=
0
;
// Count arguments
let
mut
len
=
0
;
while
!
(
*
argv
.
offset
(
len
))
.is_null
()
{
while
!
(
*
argv
.
add
(
len
))
.is_null
()
{
len
+=
1
;
}
let
mut
args
:
Vec
<&
[
u8
]
>
=
Vec
::
with_capacity
(
len
as
usize
);
let
mut
args
:
Vec
<&
[
u8
]
>
=
Vec
::
with_capacity
(
len
);
// Read shebang (for example #!/bin/sh)
let
interpreter
=
{
let
mut
reader
=
BufReader
::
new
(
&
mut
file
);
let
mut
shebang
=
[
0
;
2
];
let
mut
_interpreter_path
=
None
;
let
is_interpreted
=
{
let
mut
read
=
0
;
let
mut
shebang
=
[
0
;
2
];
while
read
<
2
{
match
reader
.read
(
&
mut
shebang
)
{
match
file
.read
(
&
mut
shebang
)
{
Ok
(
0
)
=>
break
,
Ok
(
i
)
=>
read
+=
i
,
Err
(
_
)
=>
return
-
1
,
}
}
if
&
shebang
==
b"#!"
{
// So, this file is interpreted.
// That means the actual file descriptor passed to `fexec` won't be this file.
// So we need to check ourselves that this file is actually be executable.
let
mut
stat
=
redox_stat
::
default
();
if
e
(
syscall
::
fstat
(
fd
,
&
mut
stat
))
==
!
0
{
return
-
1
;
}
let
uid
=
e
(
syscall
::
getuid
());
if
uid
==
!
0
{
return
-
1
;
}
let
gid
=
e
(
syscall
::
getuid
());
if
gid
==
!
0
{
return
-
1
;
}
let
mode
=
if
uid
==
stat
.st_uid
as
usize
{
(
stat
.st_mode
>>
3
*
2
)
&
0o7
}
else
if
gid
==
stat
.st_gid
as
usize
{
(
stat
.st_mode
>>
3
*
1
)
&
0o7
}
else
{
stat
.st_mode
&
0o7
};
if
mode
&
0o1
==
0o0
{
errno
=
EPERM
;
return
-
1
;
}
// Then, read the actual interpreter:
let
mut
interpreter
=
Vec
::
new
();
match
reader
.read_until
(
b'\n'
,
&
mut
interpreter
)
{
Err
(
_
)
=>
return
-
1
,
Ok
(
_
)
=>
{
if
interpreter
.ends_with
(
&
[
b'\n'
])
{
interpreter
.pop
()
.unwrap
();
}
// TODO: Returning the interpreter here is actually a
// hack. Preferrably we should reassign `file =`
// directly from here. Just wait until NLL comes
// around...
Some
(
interpreter
)
}
}
}
else
{
None
}
shebang
==
*
b"#!"
};
let
mut
_interpreter_path
=
None
;
if
let
Some
(
interpreter
)
=
interpreter
{
// Since the fexec implementation is almost fully done in userspace, the kernel can no
// longer set UID/GID accordingly, and this code checking for them before using
// hypothetical interfaces to upgrade UID/GID, can not be trusted. So we ask the
// `escalate:` scheme for help. Note that `escalate:` can be deliberately excluded from the
// scheme namespace to deny privilege escalation (such as su/sudo/doas) for untrusted
// processes.
//
// According to execve(2), Linux and most other UNIXes ignore setuid/setgid for interpreted
// executables and thereby simply keep the privileges as is. For compatibility we do that
// too.
if
is_interpreted
{
// So, this file is interpreted.
// Then, read the actual interpreter:
let
mut
interpreter
=
Vec
::
new
();
if
BufReader
::
new
(
&
mut
file
)
.read_until
(
b'\n'
,
&
mut
interpreter
)
.is_err
()
{
return
-
1
;
}
if
interpreter
.ends_with
(
&
[
b'\n'
])
{
interpreter
.pop
()
.unwrap
();
}
let
cstring
=
match
CString
::
new
(
interpreter
)
{
Ok
(
cstring
)
=>
cstring
,
Err
(
_
)
=>
return
-
1
,
...
...
@@ -308,21 +323,16 @@ impl Pal for Sys {
return
-
1
;
}
}
let
mut
args_envs_size_without_nul
=
0
;
// Arguments
while
!
argv
.read
()
.is_null
()
{
let
arg
=
argv
.read
();
// TODO: Optimized strlen?
let
mut
len
=
0
;
while
arg
.add
(
len
)
.read
()
!=
0
{
len
+=
1
;
}
let
len
=
strlen
(
arg
);
args
.push
(
core
::
slice
::
from_raw_parts
(
arg
as
*
const
u8
,
len
));
args_envs_size_without_nul
+=
len
;
argv
=
argv
.
offset
(
1
);
argv
=
argv
.
add
(
1
);
}
// Environment variables
...
...
@@ -335,17 +345,51 @@ impl Pal for Sys {
while
!
envp
.read
()
.is_null
()
{
let
env
=
envp
.read
();
// TODO: Optimized strlen?
let
mut
len
=
0
;
while
env
.add
(
len
)
.read
()
!=
0
{
len
+=
1
;
}
let
len
=
strlen
(
env
);
envs
.push
(
core
::
slice
::
from_raw_parts
(
env
as
*
const
u8
,
len
));
args_envs_size_without_nul
+=
len
;
envp
=
envp
.add
(
1
);
}
e
(
self
::
exec
::
fexec_impl
(
*
file
as
usize
,
path
.to_bytes
(),
&
args
,
&
envs
,
args_envs_size_without_nul
))
as
c_int
if
!
is_interpreted
&&
wants_setugid
{
let
name
=
CStr
::
from_bytes_with_nul
(
b"escalate:
\0
"
)
.expect
(
"string should be valid"
);
// We are now going to invoke `escalate:` rather than loading the program ourselves.
let
mut
escalate_fd
=
match
File
::
open
(
name
,
fcntl
::
O_WRONLY
|
fcntl
::
O_CLOEXEC
)
{
Ok
(
f
)
=>
f
,
Err
(
_
)
=>
return
-
1
,
};
// First, we write the path.
//
// TODO: For improved security, use a hypothetical SYS_DUP_FORWARD syscall to give the
// scheme our file descriptor. It can check through the kernel-overwritten stat.st_dev
// field that it pertains to a "trusted" scheme (i.e. of at least the privilege the
// new uid/gid has), although for now only root can open schemes. Passing a file
// descriptor and not a path will allow escalated to run in a limited namespace.
//
// TODO: Plus, at this point fexecve is not implemented (but specified in
// POSIX.1-2008), and to avoid bad syscalls such as fpath passing a file descriptor
// would be better.
escalate_fd
.write_all
(
path
.to_bytes
());
// Second, we write the flattened args and envs with NUL characters separating
// individual items.
if
escalate_fd
.write_all
(
&
flatten_with_nul
(
args
))
.is_err
()
{
return
-
1
;
}
if
escalate_fd
.write_all
(
&
flatten_with_nul
(
envs
))
.is_err
()
{
return
-
1
;
}
// escalated will take care of the rest when responding to SYS_CLOSE.
if
escalate_fd
.write
(
&
[])
.is_err
()
{
return
-
1
;
}
unreachable!
()
}
else
{
e
(
self
::
exec
::
fexec_impl
(
*
file
as
usize
,
path
.to_bytes
(),
&
args
,
&
envs
,
args_envs_size_without_nul
))
as
c_int
}
}
fn
fchdir
(
fd
:
c_int
)
->
c_int
{
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment