Browse Source

Major fix for a race condition vulnerability

master
Yessiest 2 weeks ago
parent
commit
d132b6a2a1
  1. 2
      examples/logging.ru
  2. 8
      examples/session.ru
  3. 1
      examples/uploader/lib
  4. 8
      lib/landline/extensions/session.rb
  5. 32
      lib/landline/path.rb
  6. 11
      lib/landline/probe.rb
  7. 23
      lib/landline/probe/handler.rb
  8. 14
      lib/landline/util/lookup.rb

2
examples/logging.ru

@ -12,7 +12,7 @@ app = Landline::Server.new do
end
path "important" do
preprocess do |req|
puts "This is a context for #{request}" if defined? request
# Implement logging logic here
puts "Client at #{req.headers['remote-addr']} wanted to access something /important!"
end

8
examples/session.ru

@ -8,23 +8,21 @@ Landline::Session.hmac_secret = "Your secure signing secret here"
app = Landline::Server.new do
get "/make_cookie" do
session["random_number"] = Random.random_number(100)
text = <<~HTML
<<~HTML
<!DOCTYPE html>
<html>
<head>
<title>
Session test #{request.to_s}
Session test
</title>
</head>
<body>
<h1>Your random number is #RAND#!</h1>
<h1>Your random number has been created!</h1>
<hr>
<p>Go check it at <a href="/check_cookie">this link!</a></p>
</body>
</html>
HTML
sleep(20)
text.gsub("#RAND#", request.to_s)
end
get "/check_cookie" do

1
examples/uploader/lib

@ -1 +0,0 @@
../../lib

8
lib/landline/extensions/session.rb

@ -22,6 +22,10 @@ module Landline
@hmac_secret ||= ENV.fetch('HMAC_SECRET', SecureRandom.base64(80))
end
# Class for representing session errors
class SessionError < ::StandardError
end
# Class for representing session storage
class Session
def initialize(cookie, cookies_callback)
@ -40,7 +44,7 @@ module Landline
# Retrieve data from session storage
# @param key [String, Symbol] serializable key
def [](key)
raise StandardError, "session not valid" unless @valid
raise Landline::Session::SessionError, "session not valid" unless @valid
unless key.is_a? String or key.is_a? Symbol
raise StandardError, "key not serializable"
@ -53,7 +57,7 @@ module Landline
# @param key [String, Symbol] serializable key
# @param value [Object] serializable data
def []=(key, value)
raise StandardError, "session not valid" unless @valid
raise Landline::Session::SessionError, "session not valid" unless @valid
unless key.is_a? String or key.is_a? Symbol
raise StandardError, "key not serializable"

32
lib/landline/path.rb

@ -31,8 +31,20 @@ module Landline
end
end
# Ephemeral proxy class to which callback execution binds
class PathExecutionOrigin
def initialize(request, properties)
@request = request
@properties = Landline::Util::LookupROProxy.new(properties)
end
attr_accessor :response
attr_reader :request, :properties
end
# Primary building block of request navigation.
class Path < Landline::Node
ExecutionOrigin = Landline::PathExecutionOrigin
ProcContext = Landline::ProcessorContext
Context = Landline::PathContext
@ -50,8 +62,6 @@ module Landline
# Contexts setup
context = self.class::Context.new(self)
context.instance_exec(&setup)
# TODO: This isn't fine
@proccontext = self.class::ProcContext.new(self)
end
# Method callback on successful request navigation.
@ -96,12 +106,19 @@ module Landline
private
# Create an execution context for in-path processing blocks
def get_context(request)
exec_origin = self.class::ExecutionOrigin.new(request, @properties)
self.class::ProcContext.new(exec_origin)
end
# Sequentially run through all filters and drop request if one is false
# @param request [Landline::Request]
# @return [Boolean] true if request passed all filters
def run_filters(request)
proccontext = get_context(request)
@filters.each do |filter|
return false unless @proccontext.instance_exec(request, &filter)
return false unless proccontext.instance_exec(request, &filter)
end
true
end
@ -109,8 +126,9 @@ module Landline
# Sequentially run all preprocessors on a request
# @param request [Landline::Request]
def run_preprocessors(request)
proccontext = get_context(request)
@preprocessors.each do |preproc|
@proccontext.instance_exec(request, &preproc)
proccontext.instance_exec(request, &preproc)
end
end
@ -126,7 +144,6 @@ module Landline
# @return [Boolean] true if further navigation will be done
# @raise [UncaughtThrowError] by default throws :response if no matches found.
def process_wrapped(request)
@request = request
return false unless run_filters(request)
run_preprocessors(request)
@ -149,7 +166,7 @@ module Landline
# @param request [Landline::Request]
def exit_stack(request, response = nil)
request.run_postprocessors(response)
false
response
end
# Try to perform indexing on the path if possible
@ -174,8 +191,9 @@ module Landline
# @param backtrace [Array(String), nil]
# @raise [UncaughtThrowError] throws :finish to stop processing
def _die(errorcode, backtrace: nil)
proccontext = get_context(request)
throw :finish, [errorcode].append(
*@proccontext.instance_exec(
*proccontext.instance_exec(
errorcode,
backtrace: backtrace,
&(@properties["handle.#{errorcode}"] or

11
lib/landline/probe.rb

@ -35,6 +35,17 @@ module Landline
end
end
# Ephemeral proxy class to which callback execution binds
class ProbeExecutionOrigin
def initialize(request, properties)
@request = request
@properties = Landline::Util::LookupROProxy.new(properties)
end
attr_accessor :response
attr_reader :request, :properties
end
# Test probe. Also base for all "reactive" nodes.
class Probe < Landline::Node
# @param path [Object]

23
lib/landline/probe/handler.rb

@ -12,8 +12,6 @@ module Landline
def initialize(path, **args, &exec)
super(path, **args)
@callback = exec
@context = Landline::ProbeContext.new(self)
@response = nil
end
attr_accessor :response
@ -34,20 +32,21 @@ module Landline
# @return [Boolean] true if further navigation is possible
# @raise [UncaughtThrowError] may raise if die() is called.
def process(request)
@response = nil
origin = Landline::ProbeExecutionOrigin.new(request, @properties)
context = Landline::ProbeContext.new(origin)
return reject(request) unless request.path.match?(/^\/?$/)
@request = request
response = catch(:break) do
@context.instance_exec(*request.splat,
**request.param,
&@callback)
context.instance_exec(*request.splat,
**request.param,
&@callback)
end
return false unless response
if @response and [String, File, IO].include? response.class
@response.body = response
throw :finish, @response
oresponse = origin.response
if oresponse and [String, File, IO].include? response.class
oresponse.body = response
throw :finish, oresponse
end
throw :finish, response
end

14
lib/landline/util/lookup.rb

@ -33,5 +33,19 @@ module Landline
attr_accessor :parent
end
# Read-only lookup proxy
class LookupROProxy
def initialize(lookup)
@lookup = lookup
end
# Get a value by key
# @param key [#hash] key for value
# @return [Object,nil]
def [](key)
@lookup.[](key)
end
end
end
end
Loading…
Cancel
Save