• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env ruby
2#
3# Copyright 2016 gRPC authors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17this_dir = File.expand_path(File.dirname(__FILE__))
18protos_lib_dir = File.join(this_dir, 'lib')
19grpc_lib_dir = File.join(File.dirname(this_dir), 'lib')
20$LOAD_PATH.unshift(grpc_lib_dir) unless $LOAD_PATH.include?(grpc_lib_dir)
21$LOAD_PATH.unshift(protos_lib_dir) unless $LOAD_PATH.include?(protos_lib_dir)
22$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
23
24require 'grpc'
25require 'end2end_common'
26
27def create_channel_creds
28  test_root = File.join(File.dirname(__FILE__), '..', 'spec', 'testdata')
29  files = ['ca.pem', 'client.key', 'client.pem']
30  creds = files.map { |f| File.open(File.join(test_root, f)).read }
31  GRPC::Core::ChannelCredentials.new(creds[0], creds[1], creds[2])
32end
33
34def client_cert
35  test_root = File.join(File.dirname(__FILE__), '..', 'spec', 'testdata')
36  cert = File.open(File.join(test_root, 'client.pem')).read
37  fail unless cert.is_a?(String)
38  cert
39end
40
41def create_server_creds
42  test_root = File.join(File.dirname(__FILE__), '..', 'spec', 'testdata')
43  GRPC.logger.info("test root: #{test_root}")
44  files = ['ca.pem', 'server1.key', 'server1.pem']
45  creds = files.map { |f| File.open(File.join(test_root, f)).read }
46  GRPC::Core::ServerCredentials.new(
47    creds[0],
48    [{ private_key: creds[1], cert_chain: creds[2] }],
49    true) # force client auth
50end
51
52# rubocop:disable Metrics/AbcSize
53# rubocop:disable Metrics/MethodLength
54def main
55  server_runner = ServerRunner.new(EchoServerImpl)
56  server_runner.server_creds = create_server_creds
57  server_port = server_runner.run
58  channel_args = {
59    GRPC::Core::Channel::SSL_TARGET => 'foo.test.google.fr'
60  }
61  token_fetch_attempts = MutableValue.new(0)
62  token_fetch_attempts_mu = Mutex.new
63  jwt_aud_uri_extraction_success_count = MutableValue.new(0)
64  jwt_aud_uri_extraction_success_count_mu = Mutex.new
65  expected_jwt_aud_uri = 'https://foo.test.google.fr/echo.EchoServer'
66  jwt_aud_uri_failure_values = []
67  times_out_first_time_auth_proc = proc do |args|
68    # We check the value of jwt_aud_uri not necessarily as a test for
69    # the correctness of jwt_aud_uri w.r.t. its expected semantics, but
70    # more for as an indirect way to check for memory corruption.
71    jwt_aud_uri_extraction_success_count_mu.synchronize do
72      if args[:jwt_aud_uri] == expected_jwt_aud_uri
73        jwt_aud_uri_extraction_success_count.value += 1
74      else
75        jwt_aud_uri_failure_values << args[:jwt_aud_uri]
76      end
77    end
78    token_fetch_attempts_mu.synchronize do
79      old_val = token_fetch_attempts.value
80      token_fetch_attempts.value += 1
81      if old_val.zero?
82        STDERR.puts 'call creds plugin sleeping for 4 seconds'
83        sleep 4
84        STDERR.puts 'call creds plugin done with 4 second sleep'
85        raise 'test exception thrown purposely from call creds plugin'
86      end
87    end
88    { 'authorization' => 'fake_val' }.merge(args)
89  end
90  channel_creds = create_channel_creds.compose(
91    GRPC::Core::CallCredentials.new(times_out_first_time_auth_proc))
92  stub = Echo::EchoServer::Stub.new("localhost:#{server_port}",
93                                    channel_creds,
94                                    channel_args: channel_args)
95  STDERR.puts 'perform a first few RPCs to try to get things into a bad state...'
96  threads = []
97  got_at_least_one_failure = MutableValue.new(false)
98  2000.times do
99    threads << Thread.new do
100      begin
101        # 2 seconds is chosen as deadline here because it is less than the 4 second
102        # sleep that the first call creds user callback does. The idea here is that
103        # a lot of RPCs will be made concurrently all with 2 second deadlines, and they
104        # will all queue up onto the call creds user callback thread, and will all
105        # have to wait for the first 4 second sleep to finish. When the deadlines
106        # of the associated calls fire ~2 seconds in, some of their C-core data
107        # will have ownership dropped, and they will hit the user-after-free in
108        # https://github.com/grpc/grpc/issues/19195 if this isn't handled correctly.
109        stub.echo(Echo::EchoRequest.new(request: 'hello'), deadline: Time.now + 2)
110      rescue GRPC::BadStatus
111        got_at_least_one_failure.value = true
112        # We don't care if these RPCs succeed or fail. The purpose of these
113        # RPCs is just to try to induce a specific use-after-free bug, and to get
114        # the call credentials callback thread into a bad state.
115      end
116    end
117  end
118  threads.each(&:join)
119  unless got_at_least_one_failure.value
120    fail 'expected at least one of the initial RPCs to fail'
121  end
122  # Expect three more RPCs to succeed
123  STDERR.puts 'now perform another RPC and expect OK...'
124  stub.echo(Echo::EchoRequest.new(request: 'hello'), deadline: Time.now + 10)
125  STDERR.puts 'now perform another RPC and expect OK...'
126  stub.echo(Echo::EchoRequest.new(request: 'hello'), deadline: Time.now + 10)
127  STDERR.puts 'now perform another RPC and expect OK...'
128  stub.echo(Echo::EchoRequest.new(request: 'hello'), deadline: Time.now + 10)
129  jwt_aud_uri_extraction_success_count_mu.synchronize do
130    if jwt_aud_uri_extraction_success_count.value < 4
131      fail "Expected auth metadata plugin callback to be ran with the jwt_aud_uri
132parameter matching its expected value at least 4 times (at least 1 out of the 2000
133initial expected-to-timeout RPCs should have caused this by now, and all three of the
134successful RPCs should have caused this). This test isn't doing what it's meant to do."
135    end
136    unless jwt_aud_uri_failure_values.empty?
137      fail "Expected to get jwt_aud_uri:#{expected_jwt_aud_uri} passed to call creds
138user callback every time that it was invoked, but it did not match the expected value
139in #{jwt_aud_uri_failure_values.size} invocations. This suggests that either:
140a) the expected jwt_aud_uri value is incorrect
141b) there is some corruption of the jwt_aud_uri argument
142Here are are the values of the jwt_aud_uri parameter that were passed to the call
143creds user callback that did not match #{expected_jwt_aud_uri}:
144#{jwt_aud_uri_failure_values}"
145    end
146  end
147  server_runner.stop
148end
149
150main
151