Soft chroot jail for sftp-server
Dimitri Nüscheler
dimitri.nuescheler at sunrise.ch
Wed Jan 1 13:12:58 EST 2014
Hi everyone
I would like to enable unprivileged users to share only certain
directories using SFTP without acquiring root, without setting
capabilities using public-key-based forced commands.
In another use case unprivileged users could write scripts that
evaluate "$SSH_ORIGINAL_COMMAND" and then either execute sftp-server
in a jail "$SSH_ORIGINAL_COMMAND" after "review" (e.g. matches a
certain regexp) and fortification.
External users would access the system in a user-defined restricted way to:
upload data, trigger processing of data, download processed data.
I created a patch against the debian sid distribution of OpenSSH
6.4-p1-1 "sftp-server.c" that introduces a command line option "-j"
(original file and patch attached)
It's not yet in a fully polished, robust and tested state, but I think
at least it is serves as PoC.
Would you accept such a patch if finalized?
Do you have any recommendations
- regarding my patch?
- to what I would like to achieve?
(basic C programming hints welcome too, I usually don't code in C,
first serious use)
Thank you & Kind Regards
Dimitri
-------------- next part --------------
75a76,78
> /* Restrict access to this directory */
> char* jail;
>
85a89,213
> /* Concatenate 2 path parts in a way that one doesn't need to care about leading/trailing slashes. Returned pointer can be freed. */
> static char* concat_path(char* parent, char* child) {
> size_t parent_len = strlen(parent);
> if (parent_len < 1)
> return xstrdup(child);
> size_t child_len = strlen(child);
> if (child_len < 1)
> return xstrdup(parent);
>
> if (*child == '/') {
> child++;
> child_len--;
> }
>
> char* cat;
> if (*(parent + parent_len - 1) == '/') {
> size_t cat_len = sizeof(char) * (parent_len + child_len + 1);
> cat = xmalloc(sizeof(char) * cat_len);
> *cat = '\0';
> strlcat(cat,parent,cat_len);
> strlcat(cat,child,cat_len);
> return cat;
> } else {
> size_t cat_len = sizeof(char) * (parent_len + child_len + 2);
> cat = xmalloc(sizeof(char) * cat_len);
> *cat = '\0';
> strlcat(cat,parent,cat_len);
> strlcat(cat,"/",cat_len);
> strlcat(cat,child,cat_len);
> return cat;
> }
> }
>
> /* shorten the path by removing occurences of "//" and any relative (backward) directory references (".", ".."), ignoring backward references
> * that traverse the current directory or root, maintaining leading "/" and maintaining trailing "/" if possible.
> * The passed string is modified.
> */
> static char* shorten_path(char* path) {
> unsigned int path_len = strlen(path)+1;
> //char* short_path = path; malloc(sizeof(char) * short_path_len);
> size_t j = 0;
> size_t i = 0;
> //Make sure path doesn't contain any "//"
> for (i = 0; i < path_len; i++) {
> if (*(path+i) == '/' && j>0 && *(path+j-1) == '/')
> j--;
>
> *(path+j) = *(path+i);
> j++;
> }
>
> j = 0;
> int dot_count = 0;
> i = 0;
> path_len = strlen(path)+1;
> char* real_path = path;
> if (*path == '/') {
> path++;
> path_len--;
> }
>
> // Execute actual ".." resolution with a path that doesn't care if it is absolute or relative
> for (i = 0; i < path_len; i++) {
> if (*(path+i) == '/' || *(path+i) == '\0') {
> if (dot_count == 2) {
> while (j>0 && *(path+j-1) != '/') {
> j--;
> }
> if (j>0) j--;
> while (j>0 && *(path+j-1) != '/') {
> j--;
> }
> if (j<1 && *(path+i) == '/') i++;
> }
> else if (dot_count == 1) {
> while (j>0 && *(path+j-1) != '/') {
> j--;
> }
> if (j<1 && *(path+i) == '/') i++;
> }
> dot_count = *(path+i) == '.' ? 1 : 0;
> }
> else if (*(path+i) == '.') dot_count++;
> else dot_count = 3;
>
> if (*(path+i) == '/' && j>0 && *(path+j-1) == '/')
> j--;
>
> *(path+j) = *(path+i);
> j++;
> }
> return real_path;
> }
> /* Takes a path from jail perspective and converts it to the actual path. This function frees path (so expects a freeable pointer) or reuses it so in any case returns a freeable pointer. */
> static char* jail_to_actual(char* path) {
> if (jail == NULL) return path;
> char* shortened = shorten_path(path);
> char* translated = concat_path(jail,shortened);
> free(shortened);
> return translated;
> }
> /* The opposite of jail_to_actual, returns NULL if path is not in jail, This function frees path (so expects a freeable pointer) or reuses it so in any case returns a freeable pointer. */
> static char* actual_to_jail(char* path) {
> if (jail == NULL) return path;
> unsigned int jail_len = strlen(jail);
> if (strncmp(jail,path,jail_len) != 0) return NULL;
> char* actual = xstrdup(path+jail_len);
> free(path);
> if (strlen(actual) < 1) {
> free(actual);
> actual = xstrdup("/");
> }
> return actual;
> }
> /* Removes trailing slashes, except a leading one, modifies the passed string */
> static void rtrim_slash(char* path) {
> unsigned int len = strlen(path);
> int i;
> for (i = len-1; i > 0; i--) {
> if (*(path+i) == '/')
> *(path+i) = '\0';
> else break;
> }
> }
>
523d650
<
552d678
<
554a681,696
> name = jail_to_actual(name);
> if (jail != NULL) {
> char resolvedname[MAXPATHLEN];
> if (realpath(name, resolvedname) == NULL) {
> send_status(id, errno_to_portable(errno));
> free(name);
> return;
> }
> char* jailed_resolvedname = actual_to_jail(xstrdup(resolvedname));
> if (jailed_resolvedname == NULL) {
> send_status(id,SSH2_FX_FAILURE);
> free(name);
> return;
> }
>
> }
589d730
<
695a837
> name = jail_to_actual(name);
771a914
> name = jail_to_actual(name);
889a1033,1048
> path = jail_to_actual(path);
> if (jail != NULL) {
> char resolvedname[MAXPATHLEN];
> if (realpath(path, resolvedname) == NULL) {
> send_status(id, errno_to_portable(errno));
> free(path);
> return;
> }
> char* jailed_resolvedname = actual_to_jail(xstrdup(resolvedname));
> if (jailed_resolvedname == NULL) {
> send_status(id,SSH2_FX_FAILURE);
> free(path);
> return;
> }
>
> }
975a1135
> name = jail_to_actual(name);
997a1158
> name = jail_to_actual(name);
1021a1183
> name = jail_to_actual(name);
1042a1205
> path = jail_to_actual(path);
1054,1055c1217,1233
< s.name = s.long_name = resolvedname;
< send_names(id, 1, &s);
---
> if (jail != NULL) {
> char* jailed_resolvedname = actual_to_jail(xstrdup(resolvedname));
> /* Note that only the resolved string needs to point inside the jail. During resolution it may visit links outside jail
> * The SFTP-user, however, is not able to create links to the outside anyway.
> */
> if (jailed_resolvedname == NULL) send_status(id,SSH2_FX_FAILURE);
> else {
> s.name = s.long_name = jailed_resolvedname;
> send_names(id, 1, &s);
> }
> free(jailed_resolvedname);
> }
> else {
> s.name = s.long_name = resolvedname;
> send_names(id, 1, &s);
> }
>
1070a1249,1250
> oldpath = jail_to_actual(oldpath);
> newpath = jail_to_actual(newpath);
1131a1312
> path = jail_to_actual(path);
1156a1338,1339
> oldpath = jail_to_actual(oldpath);
> newpath = jail_to_actual(newpath);
1178a1362,1363
> oldpath = jail_to_actual(oldpath);
> newpath = jail_to_actual(newpath);
1198a1384
> path = jail_to_actual(path);
1235a1422,1423
> oldpath = jail_to_actual(oldpath);
> newpath = jail_to_actual(newpath);
1406a1595
> jail = NULL;
1414d1602
<
1417c1605
< while (!skipargs && (ch = getopt(argc, argv, "d:f:l:u:cehR")) != -1) {
---
> while (!skipargs && (ch = getopt(argc, argv, "d:f:l:u:j:cehR")) != -1) {
1455a1644,1657
> case 'j':
> if (strlen(optarg) > 0 && *optarg == '/')
> jail = xstrdup(optarg);
> else {
> char* wd = get_current_dir_name();
> jail = concat_path(wd,optarg);
> free(wd);
> }
> rtrim_slash(jail);
> if (*jail == '\0' || (strlen(jail) == 1 && *jail == '/' )) {
> jail = NULL;
> break;
> }
> break;
1498c1700
<
---
>
1500,1501c1702,1709
< if (chdir(homedir) != 0) {
< error("chdir to \"%s\" failed: %s", homedir,
---
> char* jailed_homedir;
> if (jail != NULL) {
> jailed_homedir = concat_path(jail,homedir);
> }
> else
> jailed_homedir = xstrdup(homedir);
> if (chdir(jailed_homedir) != 0) {
> error("chdir to \"%s\" failed: %s", jailed_homedir,
1503a1712
> free(jailed_homedir);
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sftp-server.c
Type: text/x-csrc
Size: 33990 bytes
Desc: not available
URL: <http://lists.mindrot.org/pipermail/openssh-unix-dev/attachments/20140101/8703378a/attachment-0001.bin>
More information about the openssh-unix-dev
mailing list