blob: 60370251759c3f375b828a81ddd356409afdb7e3 [file] [log] [blame]
Sergei Datsenkobcd8e462018-04-20 15:44:56 +10001// Copyright 2018 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "cros-disks/sshfs_helper.h"
6
7#include <algorithm>
François Degrosa28315e2020-07-13 00:24:48 +10008#include <utility>
Sergei Datsenkobcd8e462018-04-20 15:44:56 +10009
Sergei Datsenkoad2cb6a2018-05-15 17:34:26 +100010#include <base/base64.h>
Qijiang Fan713061e2021-03-08 15:45:12 +090011#include <base/check.h>
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100012#include <base/files/file_path.h>
13#include <base/files/file_util.h>
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110014#include <base/files/scoped_temp_dir.h>
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100015#include <base/strings/string_number_conversions.h>
16#include <base/strings/string_util.h>
17
18#include "cros-disks/fuse_mounter.h"
19#include "cros-disks/mount_options.h"
Anand K Mistry25a5f852020-01-14 17:08:39 +110020#include "cros-disks/mount_point.h"
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100021#include "cros-disks/platform.h"
François Degros8b4e31e2019-07-29 11:39:19 +100022#include "cros-disks/quote.h"
Sergei Datsenko88035aa2020-11-15 00:24:01 +110023#include "cros-disks/sandboxed_process.h"
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100024#include "cros-disks/uri.h"
25
26namespace cros_disks {
27
28namespace {
29
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110030constexpr char kUserName[] = "fuse-sshfs";
31constexpr char kHelperTool[] = "/usr/bin/sshfs";
32constexpr char kType[] = "sshfs";
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100033
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110034constexpr char kOptionIdentityFile[] = "IdentityFile";
35constexpr char kOptionIdentityBase64[] = "IdentityBase64";
36constexpr char kOptionUserKnownHostsFile[] = "UserKnownHostsFile";
37constexpr char kOptionUserKnownHostsBase64[] = "UserKnownHostsBase64";
38constexpr char kOptionHostName[] = "HostName";
39constexpr char kOptionPort[] = "Port";
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100040
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110041constexpr char kIdentityFile[] = "id";
42constexpr char kUserKnownHostsFile[] = "known_hosts";
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100043
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110044OwnerUser ResolveSshfsUser(const Platform* platform) {
45 OwnerUser user;
46 PCHECK(platform->GetUserAndGroupId(kUserName, &user.uid, &user.gid));
47 return user;
48}
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100049
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110050MountErrorType WriteConfigurationFile(const Platform* platform,
51 const OwnerUser& owner,
52 const base::FilePath& path,
53 const std::string& b64_data) {
54 std::string data;
55 if (!base::Base64Decode(b64_data, &data)) {
56 LOG(ERROR) << "Invalid base64 value for " << quote(path);
57 return MOUNT_ERROR_INVALID_MOUNT_OPTIONS;
Anand K Mistry25a5f852020-01-14 17:08:39 +110058 }
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110059
60 if (platform->WriteFile(path.value(), data.c_str(), data.size()) !=
61 static_cast<int>(data.size())) {
62 PLOG(ERROR) << "Cannot write file " << quote(path);
63 return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS;
64 }
65
66 if (!platform->SetPermissions(path.value(), 0600) ||
67 !platform->SetOwnership(path.value(), owner.uid, owner.gid)) {
68 PLOG(ERROR) << "Cannot change owner of file " << quote(path);
69 return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS;
70 }
71
72 return MOUNT_ERROR_NONE;
73}
Anand K Mistry25a5f852020-01-14 17:08:39 +110074
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100075} // namespace
76
Sergei Datsenkoa910bba2019-06-18 13:31:59 +100077SshfsHelper::SshfsHelper(const Platform* platform,
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110078 brillo::ProcessReaper* process_reaper,
79 base::FilePath working_dir)
80 : FUSEMounterHelper(platform,
81 process_reaper,
82 kType,
83 /* nosymfollow= */ true,
84 &sandbox_factory_),
85 sandbox_factory_(platform,
86 SandboxedExecutable{base::FilePath(kHelperTool)},
87 ResolveSshfsUser(platform),
88 /* has_network_access= */ true),
89 working_dir_(std::move(working_dir)) {}
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100090
91SshfsHelper::~SshfsHelper() = default;
92
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110093bool SshfsHelper::CanMount(const std::string& source,
94 const std::vector<std::string>& params,
95 base::FilePath* suggested_name) const {
96 const Uri uri = Uri::Parse(source);
97 if (!uri.valid() || uri.scheme() != kType)
98 return false;
99
100 if (uri.path().empty()) {
101 *suggested_name = base::FilePath(kType);
102 } else {
103 std::string path = uri.path();
104 std::replace(path.begin(), path.end(), '/', '$');
105 std::replace(path.begin(), path.end(), '.', '_');
106 *suggested_name = base::FilePath(path);
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000107 }
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100108 return true;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000109}
110
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100111MountErrorType SshfsHelper::ConfigureSandbox(const std::string& source,
112 const base::FilePath& target_path,
113 std::vector<std::string> params,
114 SandboxedProcess* sandbox) const {
115 const Uri uri = Uri::Parse(source);
116 if (!uri.valid() || uri.scheme() != kType || uri.path().empty()) {
François Degrosc309d932021-02-04 15:12:30 +1100117 LOG(ERROR) << "Invalid source " << quote(source);
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100118 return MOUNT_ERROR_INVALID_DEVICE_PATH;
119 }
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000120
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100121 std::string b64_identity;
122 if (!GetParamValue(params, kOptionIdentityBase64, &b64_identity) ||
123 b64_identity.empty()) {
124 LOG(ERROR) << "Missing required parameter " << kOptionIdentityBase64;
125 return MOUNT_ERROR_INVALID_MOUNT_OPTIONS;
126 }
127 std::string b64_known_hosts;
128 if (!GetParamValue(params, kOptionUserKnownHostsBase64, &b64_known_hosts) ||
129 b64_known_hosts.empty()) {
130 LOG(ERROR) << "Missing required parameter " << kOptionUserKnownHostsBase64;
131 return MOUNT_ERROR_INVALID_MOUNT_OPTIONS;
132 }
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000133
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100134 std::string path;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000135
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100136 // TODO(dats): Consider plumbing hooks that would allow removing this
137 // directory after unmount.
138 if (!platform()->CreateTemporaryDirInDir(working_dir_.value(), "sshfs-",
139 &path)) {
140 PLOG(ERROR) << "Cannot create temporary directory inside "
141 << quote(working_dir_);
142 return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS;
143 }
144 base::FilePath working_dir(path);
145 base::FilePath identity_file = working_dir.Append(kIdentityFile);
146 base::FilePath known_hosts_file = working_dir.Append(kUserKnownHostsFile);
147
148 MountErrorType error = WriteConfigurationFile(
149 platform(), sandbox_factory_.run_as(), identity_file, b64_identity);
150 if (error != MOUNT_ERROR_NONE) {
151 return error;
152 }
153 error = WriteConfigurationFile(platform(), sandbox_factory_.run_as(),
154 known_hosts_file, b64_known_hosts);
155 if (error != MOUNT_ERROR_NONE) {
156 return error;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000157 }
158
159 // We retain group ownership on the directory to allow potential cleanup
160 // of its contents.
161 if (!platform()->SetPermissions(working_dir.value(), 0770) ||
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100162 !platform()->SetOwnership(working_dir.value(),
163 sandbox_factory_.run_as().uid, getgid())) {
François Degros8b4e31e2019-07-29 11:39:19 +1000164 LOG(ERROR) << "Cannot set proper ownership of working directory "
165 << quote(working_dir);
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100166 return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000167 }
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100168
169 if (!sandbox->BindMount(working_dir.value(), working_dir.value(), false,
170 false)) {
171 LOG(ERROR) << "Cannot bind working directory " << quote(working_dir);
172 return MOUNT_ERROR_INTERNAL;
173 }
174
175 std::vector<std::string> options = {
176 "KbdInteractiveAuthentication=no",
177 "PasswordAuthentication=no",
178 "BatchMode=yes",
179 "follow_symlinks",
180 "cache=no",
181 };
182
183 SetParamValue(&options, "uid", base::NumberToString(kChronosUID));
184 SetParamValue(&options, "gid", base::NumberToString(kChronosAccessGID));
185 SetParamValue(&options, kOptionIdentityFile, identity_file.value());
186 SetParamValue(&options, kOptionUserKnownHostsFile, known_hosts_file.value());
187
188 std::string value;
189 if (GetParamValue(params, kOptionHostName, &value)) {
190 SetParamValue(&options, kOptionHostName, value);
191 }
192 if (GetParamValue(params, kOptionPort, &value)) {
193 SetParamValue(&options, kOptionPort, value);
194 }
195
196 sandbox->AddArgument(uri.path());
Sergei Datsenko1bb0bdc2021-02-17 11:26:43 +1100197
198 std::string option_string;
199 if (!JoinParamsIntoOptions(options, &option_string)) {
200 return MOUNT_ERROR_INVALID_MOUNT_OPTIONS;
201 }
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100202 sandbox->AddArgument("-o");
Sergei Datsenko1bb0bdc2021-02-17 11:26:43 +1100203 sandbox->AddArgument(option_string);
204
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100205 return MOUNT_ERROR_NONE;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000206}
207
208} // namespace cros_disks