@@ -55,6 +55,26 @@ pub fn expand_path(user: Option<&expand_path::ForUser>, path: &BStr) -> Result<P
55
55
} )
56
56
}
57
57
58
+ /// Classification of a portion of a URL by whether it is *syntactically* safe to pass as an argument to a command-line program.
59
+ ///
60
+ /// Various parts of URLs can be specified to begin with `-`. If they are used as options to a command-line application
61
+ /// such as an SSH client, they will be treated as options rather than as non-option arguments as the developer intended.
62
+ /// This is a security risk, because URLs are not always trusted and can often be composed or influenced by an attacker.
63
+ /// See <https://secure.phabricator.com/T12961> for details.
64
+ ///
65
+ /// # Security Warning
66
+ ///
67
+ /// This type only expresses known *syntactic* risk. It does not cover other risks, such as passing a personal access
68
+ /// token as a username rather than a password in an application that logs usernames.
69
+ enum ArgumentSafety < ' a , T > {
70
+ /// May be safe. There is nothing to pass, so there is nothing dangerous.
71
+ Absent ,
72
+ /// May be safe. The argument does not begin with a `-` and so will not be confused as an option.
73
+ Usable ( & ' a T ) ,
74
+ /// Dangerous! Begins with `-` and could be treated as an option. Use the value in error messages only.
75
+ Dangerous ( & ' a T ) ,
76
+ }
77
+
58
78
/// A URL with support for specialized git related capabilities.
59
79
///
60
80
/// Additionally there is support for [deserialization](Url::from_bytes()) and [serialization](Url::to_bstring()).
@@ -85,7 +105,7 @@ pub struct Url {
85
105
pub port : Option < u16 > ,
86
106
/// The path portion of the URL, usually the location of the git repository.
87
107
///
88
- /// # Security- Warning
108
+ /// # Security Warning
89
109
///
90
110
/// URLs allow paths to start with `-` which makes it possible to mask command-line arguments as path which then leads to
91
111
/// the invocation of programs from an attacker controlled URL. See <https://secure.phabricator.com/T12961> for details.
@@ -164,9 +184,9 @@ impl Url {
164
184
165
185
/// Access
166
186
impl Url {
167
- /// Return the user mentioned in the URL, if present.
187
+ /// Return the username mentioned in the URL, if present.
168
188
///
169
- /// # Security- Warning
189
+ /// # Security Warning
170
190
///
171
191
/// URLs allow usernames to start with `-` which makes it possible to mask command-line arguments as username which then leads to
172
192
/// the invocation of programs from an attacker controlled URL. See <https://secure.phabricator.com/T12961> for details.
@@ -176,19 +196,28 @@ impl Url {
176
196
self . user . as_deref ( )
177
197
}
178
198
179
- /// Return the user from this URL if present *and* if it can't be mistaken for a command-line argument.
199
+ /// Classify the username of this URL by whether it is safe to pass as a command-line argument.
180
200
///
181
- /// Use this method if the user or a portion of the URL that begins with it will be passed to a command-line application.
201
+ /// Use this method instead of [Self::user()] if the host is going to be passed to a command-line application.
202
+ /// If the unsafe and absent cases need not be distinguished, [Self::user_argument_safe()] may also be used.
203
+ pub fn user_as_argument ( & self ) -> ArgumentSafety < str > {
204
+ match self . user ( ) {
205
+ Some ( user) if looks_like_command_line_option ( user. as_bytes ( ) ) => ArgumentSafety :: Dangerous ( user) ,
206
+ Some ( user) => ArgumentSafety :: Usable ( user) ,
207
+ None => ArgumentSafety :: Absent ,
208
+ }
209
+ }
210
+
211
+ /// Return the username of this URL if present *and* if it can't be mistaken for a command-line argument.
212
+ ///
213
+ /// Use this method or [Self::user_as_argument()] instead of [Self::user()] if the host is going to be
214
+ /// passed to a command-line application. Prefer [Self::user_as_argument()] unless the unsafe and absent
215
+ /// cases need not be distinguished from each other.
182
216
pub fn user_argument_safe ( & self ) -> Option < & str > {
183
- // FIXME: A return value of None from this method, or host_argument_safe(), is ambiguous: the user (or host) is
184
- // either present but unsafe, or absent. Furthermore, in practice the value is usually needed even if unsafe,
185
- // in order to report it in an error message. In gix-transport, the ambiguity makes it easy to write a new bug
186
- // while using this interface for user_argument_safe(). In contrast, in host_argument_safe(), the ambiguity is
187
- // much less of a problem, because the host is expected to be present. Yet the host() method must still be
188
- // called when handling the None case, to include it in the error. If possible, both methods should be replaced
189
- // by methods with a richer return type (a new enum). If not, the ambiguity should be prominently documented.
190
- self . user ( )
191
- . filter ( |user| !looks_like_command_line_option ( user. as_bytes ( ) ) )
217
+ match self . user_as_argument ( ) {
218
+ ArgumentSafety :: Usable ( user) => Some ( user) ,
219
+ _ => None ,
220
+ }
192
221
}
193
222
194
223
/// Return the password mentioned in the url, if present.
@@ -198,28 +227,63 @@ impl Url {
198
227
199
228
/// Return the host mentioned in the URL, if present.
200
229
///
201
- /// # Security- Warning
230
+ /// # Security Warning
202
231
///
203
232
/// URLs allow hosts to start with `-` which makes it possible to mask command-line arguments as host which then leads to
204
233
/// the invocation of programs from an attacker controlled URL. See <https://secure.phabricator.com/T12961> for details.
205
234
///
206
- /// If this value is ever going to be passed to a command-line application, call [Self::host_argument_safe()] instead.
235
+ /// If this value is ever going to be passed to a command-line application, call [Self::host_as_argument()]
236
+ /// or [Self::host_argument_safe()] instead.
207
237
pub fn host ( & self ) -> Option < & str > {
208
238
self . host . as_deref ( )
209
239
}
210
240
241
+ /// Classify the host of this URL by whether it is safe to pass as a command-line argument.
242
+ ///
243
+ /// Use this method instead of [Self::host()] if the host is going to be passed to a command-line application.
244
+ /// If the unsafe and absent cases need not be distinguished, [Self::host_argument_safe()] may also be used.
245
+ pub fn host_as_argument ( & self ) -> ArgumentSafety < str > {
246
+ match self . host ( ) {
247
+ Some ( host) if looks_like_command_line_option ( host. as_bytes ( ) ) => ArgumentSafety :: Dangerous ( host) ,
248
+ Some ( host) => ArgumentSafety :: Usable ( host) ,
249
+ None => ArgumentSafety :: Absent ,
250
+ }
251
+ }
252
+
211
253
/// Return the host of this URL if present *and* if it can't be mistaken for a command-line argument.
212
254
///
213
- /// Use this method if the host is going to be passed to a command-line application.
255
+ /// Use this method or [Self::host_as_argument()] instead of [Self::host()] if the host is going to be
256
+ /// passed to a command-line application. Prefer [Self::host_as_argument()] unless the unsafe and absent
257
+ /// cases need not be distinguished from each other.
214
258
pub fn host_argument_safe ( & self ) -> Option < & str > {
215
- self . host ( )
216
- . filter ( |host| !looks_like_command_line_option ( host. as_bytes ( ) ) )
259
+ match self . host_as_argument ( ) {
260
+ ArgumentSafety :: Usable ( host) => Some ( host) ,
261
+ _ => None ,
262
+ }
263
+ }
264
+
265
+ /// Classify the path of this URL by whether it is safe to pass as a command-line argument.
266
+ /// Note that it always begins with a slash, which is ignored for this classification.
267
+ ///
268
+ /// Use this method or [Self::path_argument_safe()] instead of [Self::path] if the host is going to be
269
+ /// passed to a command-line application, unless it is certain that the leading `/` will always be included.
270
+ ///
271
+ /// This method never returns an [ArgumentSafety::Absent].
272
+ pub fn path_as_argument ( & self ) -> ArgumentSafety < BStr > {
273
+ match self . path_argument_safe ( ) {
274
+ Some ( path) => ArgumentSafety :: Usable ( path) ,
275
+ None => ArgumentSafety :: Dangerous ( self . path . as_ref ( ) ) ,
276
+ }
217
277
}
218
278
219
279
/// Return the path of this URL *if* it can't be mistaken for a command-line argument.
220
280
/// Note that it always begins with a slash, which is ignored for this comparison.
221
281
///
222
- /// Use this method if the path is going to be passed to a command-line application.
282
+ /// Use this method instead of accessing [Self::path] directly if the path is going to be passed to a
283
+ /// command-line application, unless it is certain that the leading `/` will always be included.
284
+ ///
285
+ /// The result of this method is unambiguous because [Self::path] is not an option type, but
286
+ /// [Self::path_as_argument()] may also safely be used.
223
287
pub fn path_argument_safe ( & self ) -> Option < & BStr > {
224
288
self . path
225
289
. get ( 1 ..)
0 commit comments