diff --git a/repos/libports/src/lib/libc/vfs_plugin.cc b/repos/libports/src/lib/libc/vfs_plugin.cc index d384fce907..8c06b5a740 100644 --- a/repos/libports/src/lib/libc/vfs_plugin.cc +++ b/repos/libports/src/lib/libc/vfs_plugin.cc @@ -706,7 +706,7 @@ int Libc::Vfs_plugin::symlink(const char *oldpath, const char *newpath) case Result::SYMLINK_ERR_EXISTS: errno = EEXIST; return -1; case Result::SYMLINK_ERR_NO_ENTRY: errno = ENOENT; return -1; case Result::SYMLINK_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; return -1; - case Result::SYMLINK_ERR_NO_PERM: errno = ENOSYS; return -1; + case Result::SYMLINK_ERR_NO_PERM: errno = EPERM; return -1; case Result::SYMLINK_ERR_NO_SPACE: errno = ENOSPC; return -1; case Result::SYMLINK_OK: break; } diff --git a/repos/os/src/lib/vfs/fs_file_system.h b/repos/os/src/lib/vfs/fs_file_system.h index 0403c81f87..8d89e706de 100644 --- a/repos/os/src/lib/vfs/fs_file_system.h +++ b/repos/os/src/lib/vfs/fs_file_system.h @@ -530,6 +530,8 @@ class Vfs::Fs_file_system : public File_system Symlink_result symlink(char const *from, char const *to) override { + auto const from_len = strlen(from); + /* * We write to the symlink via the packet stream. Hence we need * to serialize with other packet-stream operations. @@ -550,10 +552,18 @@ class Vfs::Fs_file_system : public File_system Fs_handle_guard from_dir_guard(*this, _fs, dir_handle, _handle_space); ::File_system::Symlink_handle symlink_handle = - _fs.symlink(dir_handle, symlink_name.base() + 1, true); + _fs.symlink(dir_handle, symlink_name.base() + 1, true); Fs_handle_guard symlink_guard(*this, _fs, symlink_handle, _handle_space); - _write(symlink_guard, from, strlen(from) + 1, 0); + auto const n = _write(symlink_guard, from, from_len, 0); + + /* + * a convention at the VFS server is to return an invalid + * result length when the target is too long + */ + if (n != from_len) { + return n ? SYMLINK_ERR_NAME_TOO_LONG : SYMLINK_ERR_NO_PERM; + } } catch (::File_system::Invalid_handle) { return SYMLINK_ERR_NO_ENTRY; } catch (::File_system::Node_already_exists) { return SYMLINK_ERR_EXISTS; } diff --git a/repos/os/src/lib/vfs/ram_file_system.h b/repos/os/src/lib/vfs/ram_file_system.h index d39c3f75ed..10d4e10b80 100644 --- a/repos/os/src/lib/vfs/ram_file_system.h +++ b/repos/os/src/lib/vfs/ram_file_system.h @@ -246,7 +246,7 @@ class Vfs_ram::Symlink : public Vfs_ram::Node void set(char const *target, size_t len) { - _len = min(len, MAX_PATH_LEN); + _len = len; memcpy(_target, target, _len); } @@ -579,6 +579,10 @@ class Vfs::Ram_file_system : public Vfs::File_system { using namespace Vfs_ram; + auto const target_len = strlen(target); + if (target_len > MAX_PATH_LEN) + return SYMLINK_ERR_NAME_TOO_LONG; + Symlink *link; Directory *parent = lookup_parent(path); if (!parent) return SYMLINK_ERR_NO_ENTRY; @@ -606,7 +610,7 @@ class Vfs::Ram_file_system : public Vfs::File_system } if (*target) - link->set(target, strlen(target)); + link->set(target, target_len); link->unlock(); return SYMLINK_OK; } diff --git a/repos/os/src/server/ram_fs/symlink.h b/repos/os/src/server/ram_fs/symlink.h index 0491969c50..5626864250 100644 --- a/repos/os/src/server/ram_fs/symlink.h +++ b/repos/os/src/server/ram_fs/symlink.h @@ -43,8 +43,6 @@ class Ram_fs::Symlink : public Node /* Ideal symlink operations are atomic. */ if (seek_offset) return 0; - len = min(len, sizeof(_link_to)); - for (size_t i = 0; i < len; ++i) { if (src[i] == '\0') { len = i; @@ -52,6 +50,13 @@ class Ram_fs::Symlink : public Node } } + /* + * if the target is too long return a + * short result to indicate the error + */ + if (len > sizeof(_link_to)) + return len >> 1; + Genode::memcpy(_link_to, src, len); _len = len; return len; diff --git a/repos/os/src/server/vfs/node.h b/repos/os/src/server/vfs/node.h index 8768e5357f..afb3cfb03d 100644 --- a/repos/os/src/server/vfs/node.h +++ b/repos/os/src/server/vfs/node.h @@ -132,15 +132,30 @@ struct Vfs_server::Symlink : Node size_t write(Vfs::File_system &vfs, char const *src, size_t len, seek_off_t seek_offset) { - /* ensure symlink gets something null-terminated */ - Genode::String target(Genode::Cstring(src, len)); + /* + * if the symlink target is too long return a short result + * because a competent File_system client will error on a + * length mismatch + */ - if (vfs.symlink(target.string(), path()) == Directory_service::SYMLINK_OK) - return 0; + if (len > MAX_PATH_LEN) { + return len >> 1; + } + + /* ensure symlink gets something null-terminated */ + Genode::String target(Genode::Cstring(src, len)); + size_t const target_len = target.length()-1; + + switch (vfs.symlink(target.string(), path())) { + case Directory_service::SYMLINK_OK: break; + case Directory_service::SYMLINK_ERR_NAME_TOO_LONG: + return target_len >> 1; + default: return 0; + } mark_as_updated(); notify_listeners(); - return target.length(); + return target_len; } bool read_ready() override { return true; }