@@ -187,8 +187,86 @@ def test_cgi_session_filestore_path
187187 assert_equal path_sha512 , path
188188 end
189189
190+ def test_rejects_insecure_session_file
191+ session_id = "insecure"
192+ path = session_file_store_path ( "tmpdir" => @session_dir ,
193+ "session_id" => session_id )
194+ File . write ( path , "" )
195+ File . chmod ( 0644 , path )
196+
197+ assert_raise ( CGI ::Session ::UnsafeSessionFileError ) do
198+ CGI ::Session . new ( Object . new , "tmpdir" => @session_dir ,
199+ "session_id" => session_id )
200+ end
201+ end unless CGI ::Session . const_get ( :MODE_MASK ) . zero?
202+
203+ def test_filestore_update_rejects_existing_new_file
204+ session = CGI ::Session . new ( Object . new , "tmpdir" => @session_dir ,
205+ "session_id" => "stale" )
206+ path = session . instance_variable_get ( :@dbman ) . instance_variable_get ( :@path )
207+ new_path = path + ".new"
208+ File . write ( new_path , "existing" )
209+ session [ "key" ] = "value"
210+
211+ assert_raise ( Errno ::EEXIST ) do
212+ session . close
213+ end
214+ assert_equal ( "existing" , File . read ( new_path ) )
215+ ensure
216+ session . delete if session
217+ end
218+
219+ def test_pstore_does_not_enable_thread_safety_for_compatibility
220+ session = CGI ::Session . new ( Object . new , "tmpdir" => @session_dir ,
221+ "session_id" => "pstore-compat" ,
222+ "database_manager" => CGI ::Session ::PStore )
223+ pstore = session . instance_variable_get ( :@dbman ) . instance_variable_get ( :@p )
224+
225+ assert_equal ( false , pstore . instance_variable_get ( :@thread_safe ) )
226+ ensure
227+ session . delete if session
228+ end if defined? ( ::PStore )
229+
230+ def test_pstore_rejects_session_file_replaced_by_symlink
231+ omit ( "O_NOFOLLOW is not supported" ) unless nofollow_supported?
232+
233+ session = CGI ::Session . new ( Object . new , "tmpdir" => @session_dir ,
234+ "session_id" => "pstore-symlink" ,
235+ "database_manager" => CGI ::Session ::PStore )
236+ session [ "key" ] = "secret"
237+ pstore = session . instance_variable_get ( :@dbman ) . instance_variable_get ( :@p )
238+ target = File . join ( @session_dir , "target" )
239+ File . write ( target , "" )
240+ File . unlink ( pstore . path )
241+ File . symlink ( "target" , pstore . path )
242+
243+ assert_raise ( Errno ::ELOOP ) do
244+ session . close
245+ end
246+ assert_empty ( File . read ( target ) )
247+ ensure
248+ session . delete if session
249+ end if defined? ( ::PStore ) and !CGI ::Session ::PStore ::PSTORE_OPT . empty?
250+
190251 private
191252
253+ def nofollow_supported?
254+ return false unless File . const_defined? ( :NOFOLLOW )
255+
256+ target = File . join ( @session_dir , "nofollow-target" )
257+ link = File . join ( @session_dir , "nofollow-link" )
258+ File . write ( target , "" )
259+ File . symlink ( target , link )
260+ File . open ( link , File ::RDONLY |File ::NOFOLLOW ) . close
261+ false
262+ rescue Errno ::ELOOP
263+ true
264+ rescue NotImplementedError , SystemCallError
265+ false
266+ ensure
267+ [ target , link ] . each { |file | File . unlink ( file ) if file }
268+ end
269+
192270 def assert_session_filestore_path ( path , dir : @session_dir , prefix : "cgi_sid_" , suffix : nil )
193271 base = File . basename ( path )
194272 assert_equal dir , File . dirname ( path )
0 commit comments