Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
f5302617
Commit
f5302617
authored
Oct 16, 2017
by
Andrew Newdigate
Committed by
Sean McGivern
Oct 16, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Popen with a timeout
parent
fde837d5
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
208 additions
and
1 deletion
+208
-1
app/models/repository.rb
app/models/repository.rb
+1
-1
changelogs/unreleased/an-popen-deadline.yml
changelogs/unreleased/an-popen-deadline.yml
+5
-0
lib/gitlab/git/popen.rb
lib/gitlab/git/popen.rb
+63
-0
lib/gitlab/git/repository.rb
lib/gitlab/git/repository.rb
+7
-0
spec/lib/gitlab/git/popen_spec.rb
spec/lib/gitlab/git/popen_spec.rb
+132
-0
No files found.
app/models/repository.rb
View file @
f5302617
...
...
@@ -1127,7 +1127,7 @@ class Repository
def
last_commit_id_for_path_by_shelling_out
(
sha
,
path
)
args
=
%W(rev-list --max-count=1
#{
sha
}
--
#{
path
}
)
r
un_git
(
args
).
first
.
strip
r
aw_repository
.
run_git_with_timeout
(
args
,
Gitlab
::
Git
::
Popen
::
FAST_GIT_PROCESS_TIMEOUT
).
first
.
strip
end
def
repository_storage_path
...
...
changelogs/unreleased/an-popen-deadline.yml
0 → 100644
View file @
f5302617
---
title
:
Use a timeout on certain git operations
merge_request
:
14872
author
:
type
:
security
lib/gitlab/git/popen.rb
View file @
f5302617
...
...
@@ -5,6 +5,8 @@ require 'open3'
module
Gitlab
module
Git
module
Popen
FAST_GIT_PROCESS_TIMEOUT
=
15
.
seconds
def
popen
(
cmd
,
path
,
vars
=
{})
unless
cmd
.
is_a?
(
Array
)
raise
"System commands must be given as an array of strings"
...
...
@@ -27,6 +29,67 @@ module Gitlab
[
@cmd_output
,
@cmd_status
]
end
def
popen_with_timeout
(
cmd
,
timeout
,
path
,
vars
=
{})
unless
cmd
.
is_a?
(
Array
)
raise
"System commands must be given as an array of strings"
end
path
||=
Dir
.
pwd
vars
[
'PWD'
]
=
path
unless
File
.
directory?
(
path
)
FileUtils
.
mkdir_p
(
path
)
end
rout
,
wout
=
IO
.
pipe
rerr
,
werr
=
IO
.
pipe
pid
=
Process
.
spawn
(
vars
,
*
cmd
,
out:
wout
,
err:
werr
,
chdir:
path
,
pgroup:
true
)
begin
status
=
process_wait_with_timeout
(
pid
,
timeout
)
# close write ends so we could read them
wout
.
close
werr
.
close
cmd_output
=
rout
.
readlines
.
join
cmd_output
<<
rerr
.
readlines
.
join
# Copying the behaviour of `popen` which merges stderr into output
[
cmd_output
,
status
.
exitstatus
]
rescue
Timeout
::
Error
=>
e
kill_process_group_for_pid
(
pid
)
raise
e
ensure
wout
.
close
unless
wout
.
closed?
werr
.
close
unless
werr
.
closed?
rout
.
close
rerr
.
close
end
end
def
process_wait_with_timeout
(
pid
,
timeout
)
deadline
=
timeout
.
seconds
.
from_now
wait_time
=
0.01
while
deadline
>
Time
.
now
sleep
(
wait_time
)
_
,
status
=
Process
.
wait2
(
pid
,
Process
::
WNOHANG
)
return
status
unless
status
.
nil?
end
raise
Timeout
::
Error
,
"Timeout waiting for process #
#{
pid
}
"
end
def
kill_process_group_for_pid
(
pid
)
Process
.
kill
(
"KILL"
,
-
pid
)
Process
.
wait
(
pid
)
rescue
Errno
::
ESRCH
end
end
end
end
lib/gitlab/git/repository.rb
View file @
f5302617
...
...
@@ -1071,6 +1071,13 @@ module Gitlab
end
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def
run_git_with_timeout
(
args
,
timeout
,
env:
{})
circuit_breaker
.
perform
do
popen_with_timeout
([
Gitlab
.
config
.
git
.
bin_path
,
*
args
],
timeout
,
path
,
env
)
end
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def
commit
(
ref
=
'HEAD'
)
Gitlab
::
Git
::
Commit
.
find
(
self
,
ref
)
...
...
spec/lib/gitlab/git/popen_spec.rb
0 → 100644
View file @
f5302617
require
'spec_helper'
describe
'Gitlab::Git::Popen'
do
let
(
:path
)
{
Rails
.
root
.
join
(
'tmp'
).
to_s
}
let
(
:klass
)
do
Class
.
new
(
Object
)
do
include
Gitlab
::
Git
::
Popen
end
end
context
'popen'
do
context
'zero status'
do
let
(
:result
)
{
klass
.
new
.
popen
(
%w(ls)
,
path
)
}
let
(
:output
)
{
result
.
first
}
let
(
:status
)
{
result
.
last
}
it
{
expect
(
status
).
to
be_zero
}
it
{
expect
(
output
).
to
include
(
'tests'
)
}
end
context
'non-zero status'
do
let
(
:result
)
{
klass
.
new
.
popen
(
%w(cat NOTHING)
,
path
)
}
let
(
:output
)
{
result
.
first
}
let
(
:status
)
{
result
.
last
}
it
{
expect
(
status
).
to
eq
(
1
)
}
it
{
expect
(
output
).
to
include
(
'No such file or directory'
)
}
end
context
'unsafe string command'
do
it
'raises an error when it gets called with a string argument'
do
expect
{
klass
.
new
.
popen
(
'ls'
,
path
)
}.
to
raise_error
(
RuntimeError
)
end
end
context
'with custom options'
do
let
(
:vars
)
{
{
'foobar'
=>
123
,
'PWD'
=>
path
}
}
let
(
:options
)
{
{
chdir:
path
}
}
it
'calls popen3 with the provided environment variables'
do
expect
(
Open3
).
to
receive
(
:popen3
).
with
(
vars
,
'ls'
,
options
)
klass
.
new
.
popen
(
%w(ls)
,
path
,
{
'foobar'
=>
123
})
end
end
context
'use stdin'
do
let
(
:result
)
{
klass
.
new
.
popen
(
%w[cat]
,
path
)
{
|
stdin
|
stdin
.
write
'hello'
}
}
let
(
:output
)
{
result
.
first
}
let
(
:status
)
{
result
.
last
}
it
{
expect
(
status
).
to
be_zero
}
it
{
expect
(
output
).
to
eq
(
'hello'
)
}
end
end
context
'popen_with_timeout'
do
let
(
:timeout
)
{
1
.
second
}
context
'no timeout'
do
context
'zero status'
do
let
(
:result
)
{
klass
.
new
.
popen_with_timeout
(
%w(ls)
,
timeout
,
path
)
}
let
(
:output
)
{
result
.
first
}
let
(
:status
)
{
result
.
last
}
it
{
expect
(
status
).
to
be_zero
}
it
{
expect
(
output
).
to
include
(
'tests'
)
}
end
context
'non-zero status'
do
let
(
:result
)
{
klass
.
new
.
popen_with_timeout
(
%w(cat NOTHING)
,
timeout
,
path
)
}
let
(
:output
)
{
result
.
first
}
let
(
:status
)
{
result
.
last
}
it
{
expect
(
status
).
to
eq
(
1
)
}
it
{
expect
(
output
).
to
include
(
'No such file or directory'
)
}
end
context
'unsafe string command'
do
it
'raises an error when it gets called with a string argument'
do
expect
{
klass
.
new
.
popen_with_timeout
(
'ls'
,
timeout
,
path
)
}.
to
raise_error
(
RuntimeError
)
end
end
end
context
'timeout'
do
context
'timeout'
do
it
"raises a Timeout::Error"
do
expect
{
klass
.
new
.
popen_with_timeout
(
%w(sleep 1000)
,
timeout
,
path
)
}.
to
raise_error
(
Timeout
::
Error
)
end
it
"handles processes that do not shutdown correctly"
do
expect
{
klass
.
new
.
popen_with_timeout
([
'bash'
,
'-c'
,
"trap -- '' SIGTERM; sleep 1000"
],
timeout
,
path
)
}.
to
raise_error
(
Timeout
::
Error
)
end
end
context
'timeout period'
do
let
(
:time_taken
)
do
begin
start
=
Time
.
now
klass
.
new
.
popen_with_timeout
(
%w(sleep 1000)
,
timeout
,
path
)
rescue
Time
.
now
-
start
end
end
it
{
expect
(
time_taken
).
to
be
>=
timeout
}
end
context
'clean up'
do
let
(
:instance
)
{
klass
.
new
}
it
'kills the child process'
do
expect
(
instance
).
to
receive
(
:kill_process_group_for_pid
).
and_wrap_original
do
|
m
,
*
args
|
# is the PID, and it should not be running at this point
m
.
call
(
*
args
)
pid
=
args
.
first
begin
Process
.
getpgid
(
pid
)
raise
"The child process should have been killed"
rescue
Errno
::
ESRCH
end
end
expect
{
instance
.
popen_with_timeout
([
'bash'
,
'-c'
,
"trap -- '' SIGTERM; sleep 1000"
],
timeout
,
path
)
}.
to
raise_error
(
Timeout
::
Error
)
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment