summaryrefslogtreecommitdiff
blob: 697868823e52ef02fd8ff325874297bb0b8f3fb5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
http://lists.gnu.org/archive/html/emacs-devel/2012-01/msg00387.html
https://bugs.gentoo.org/398227

Hiroshi Oota has found a security flaw in EDE (part of CEDET), a
development tool included in Emacs.  EDE can store various information
about a project, such as how to build the project, in a file named
Project.ede in the project directory tree.  When the minor mode
`global-ede-mode' is enabled, visiting a file causes Emacs to look for
Project.ede in the file's directory or one of its parent directories.
If Project.ede is present, Emacs automatically reads and evaluates the
first Lisp expression in it.

This design exposes EDE users to the danger of loading malicious code
from one file (Project.ede), simply by visiting another file in the same
directory tree.

A patch to fix this problem, for the Emacs 23.3 release, is attached.
It prevents EDE from loading Project.ede files, except in directories
explicitly designated as "safe" by the user via the new list variable
`ede-project-directories'.  The value of this variable is initially the
empty list; Emacs offers to add to it when the user invokes the `M-x
ede' or `M-x ede-new' command.  EDE project types that do not use
Project.ede (e.g. those that scan makefiles for build information) are
unaffected, since they do not involve loading Lisp code.

--- emacs-23.3-orig/lisp/cedet/ede/auto.el
+++ emacs-23.3/lisp/cedet/ede/auto.el
@@ -58,6 +58,13 @@
 	  :initform t
 	  :documentation
 	  "Non-nil if this is an option when a user creates a project.")
+   (safe-p :initarg :safe-p
+	   :initform t
+	   :documentation
+	   "Non-nil if the project load files are \"safe\".
+An unsafe project is one that loads project variables via Emacs
+Lisp code.  A safe project is one that loads project variables by
+scanning files without loading Lisp code from them.")
    )
   "Class representing minimal knowledge set to run preliminary EDE functions.
 When more advanced functionality is needed from a project type, that projects
@@ -69,13 +76,15 @@
 			 :name "Make" :file 'ede/proj
 			 :proj-file "Project.ede"
 			 :load-type 'ede-proj-load
-			 :class-sym 'ede-proj-project)
+			 :class-sym 'ede-proj-project
+			 :safe-p nil)
    (ede-project-autoload "edeproject-automake"
 			 :name "Automake" :file 'ede/proj
 			 :proj-file "Project.ede"
 			 :initializers '(:makefile-type Makefile.am)
 			 :load-type 'ede-proj-load
-			 :class-sym 'ede-proj-project)
+			 :class-sym 'ede-proj-project
+			 :safe-p nil)
    (ede-project-autoload "automake"
 			 :name "automake" :file 'ede/project-am
 			 :proj-file "Makefile.am"
@@ -84,6 +93,8 @@
 			 :new-p nil))
   "List of vectors defining how to determine what type of projects exist.")
 
+(put 'ede-project-class-files 'risky-local-variable t)
+
 ;;; EDE project-autoload methods
 ;;
 (defmethod ede-project-root ((this ede-project-autoload))
@@ -122,6 +133,19 @@
     (when (and f (file-exists-p f))
       f)))
 
+(defmethod ede-auto-load-project ((this ede-project-autoload) dir)
+  "Load in the project associated with THIS project autoload description.
+THIS project description should be valid for DIR, where the project will
+be loaded."
+  ;; Last line of defense: don't load unsafe projects.
+  (when (not (or (oref this :safe-p)
+		 (ede-directory-safe-p dir)))
+    (error "Attempt to load an unsafe project (bug elsewhere in EDE)"))
+  ;; Things are good - so load the project.
+  (let ((o (funcall (oref this load-type) dir)))
+    (when (not o)
+      (error "Project type error: :load-type failed to create a project"))
+    (ede-add-project-to-global-list o)))
 
 (provide 'ede/auto)
 
--- emacs-23.3-orig/lisp/cedet/ede/simple.el
+++ emacs-23.3/lisp/cedet/ede/simple.el
@@ -50,7 +50,8 @@
 	      :name "Simple" :file 'ede/simple
 	      :proj-file 'ede-simple-projectfile-for-dir
 	      :load-type 'ede-simple-load
-	      :class-sym 'ede-simple-project)
+	      :class-sym 'ede-simple-project
+	      :safe-p nil)
 	     t)
 
 (defcustom ede-simple-save-directory "~/.ede"
--- emacs-23.3-orig/lisp/cedet/ede.el
+++ emacs-23.3/lisp/cedet/ede.el
@@ -94,6 +94,42 @@
   :group 'ede
   :type 'sexp) ; make this be a list of options some day
 
+(defcustom ede-project-directories nil
+  "Directories in which EDE may search for project files.
+If the value is t, EDE may search in any directory.
+
+If the value is a function, EDE calls that function with one
+argument, the directory name; the function should return t iff
+EDE should look for project files in the directory.
+
+Otherwise, the value should be a list of fully-expanded directory
+names.  EDE searches for project files only in those directories.
+If you invoke the commands \\[ede] or \\[ede-new] on a directory
+that is not listed, Emacs will offer to add it to the list.
+
+Any other value disables searching for EDE project files."
+  :group 'ede
+  :type '(choice (const :tag "Any directory" t)
+		 (repeat :tag "List of directories"
+			 (directory))
+		 (function :tag "Predicate"))
+  :version "23.4"
+  :risky t)
+
+(defun ede-directory-safe-p (dir)
+  "Return non-nil if DIR is a safe directory to load projects from.
+Projects that do not load a project definition as Emacs Lisp code
+are safe, and can be loaded automatically.  Other project types,
+such as those created with Project.ede files, are safe only if
+specified by `ede-project-directories'."
+  (setq dir (directory-file-name (expand-file-name dir)))
+  ;; Load only if allowed by `ede-project-directories'.
+  (or (eq ede-project-directories t)
+      (and (functionp ede-project-directories)
+	   (funcall ede-project-directories dir))
+      (and (listp ede-project-directories)
+	   (member dir ede-project-directories))))
+
 
 ;;; Management variables
 
@@ -419,24 +455,42 @@
 Sets buffer local variables for EDE."
   (let* ((ROOT nil)
 	 (proj (ede-directory-get-open-project default-directory
-					       'ROOT)))
+					       'ROOT))
+	 (projauto nil))
+
     (when (or proj ROOT
-	      (ede-directory-project-p default-directory t))
+	      ;; If there is no open project, look up the project
+	      ;; autoloader to see if we should initialize.
+	      (setq projauto (ede-directory-project-p default-directory t)))
+
+      (when (and (not proj) projauto)
+
+	;; No project was loaded, but we have a project description
+	;; object.  This means that we can check if it is a safe
+	;; project to load before requesting it to be loaded.
+
+	(when (or (oref projauto safe-p)
+		  ;; The project style is not safe, so check if it is
+		  ;; in `ede-project-directories'.
+		  (let ((top (ede-toplevel-project default-directory)))
+		    (ede-directory-safe-p top)))
 
-      (when (not proj)
-	;; @todo - this could be wasteful.
-	(setq proj (ede-load-project-file default-directory 'ROOT)))
+	  ;; The project is safe, so load it in.
+	  (setq proj (ede-load-project-file default-directory 'ROOT))))
 
-      (setq ede-object (ede-buffer-object (current-buffer)
+      ;; Only initialize EDE state in this buffer if we found a project.
+      (when proj
+
+	(setq ede-object (ede-buffer-object (current-buffer)
 					  'ede-object-project))
 
-      (setq ede-object-root-project
-	    (or ROOT (ede-project-root ede-object-project)))
+	(setq ede-object-root-project
+	      (or ROOT (ede-project-root ede-object-project)))
 
-      (if (and (not ede-object) ede-object-project)
-	  (ede-auto-add-to-target))
+	(if (and (not ede-object) ede-object-project)
+	    (ede-auto-add-to-target))
 
-      (ede-apply-target-options))))
+	(ede-apply-target-options)))))
 
 (defun ede-reset-all-buffers (onoff)
   "Reset all the buffers due to change in EDE.
@@ -555,13 +609,73 @@
 
 ;;; Interactive method invocations
 ;;
-(defun ede (file)
-  "Start up EDE on something.
-Argument FILE is the file or directory to load a project from."
-  (interactive "fProject File: ")
-  (if (not (file-exists-p file))
-      (ede-new file)
-    (ede-load-project-file (file-name-directory file))))
+(defun ede (dir)
+  "Start up EDE for directory DIR.
+If DIR has an existing project file, load it.
+Otherwise, create a new project for DIR."
+  (interactive
+   ;; When choosing a directory to turn on, and we see some directory here,
+   ;; provide that as the default.
+   (let* ((top (ede-toplevel-project default-directory))
+	  (promptdflt (or top default-directory)))
+     (list (read-directory-name "Project directory: "
+				promptdflt promptdflt t))))
+  (unless (file-directory-p dir)
+    (error "%s is not a directory" dir))
+  (when (ede-directory-get-open-project dir)
+    (error "%s already has an open project associated with it" dir))
+
+  ;; Check if the directory has been added to the list of safe
+  ;; directories.  It can also add the directory to the safe list if
+  ;; the user chooses.
+  (if (ede-check-project-directory dir)
+      (progn
+	;; If there is a project in DIR, load it, otherwise do
+	;; nothing.
+	(ede-load-project-file dir)
+
+	;; Check if we loaded anything on the previous line.
+	(if (ede-current-project dir)
+
+	    ;; We successfully opened an existing project.  Some open
+	    ;; buffers may also be referring to this project.
+	    ;; Resetting all the buffers will get them to also point
+	    ;; at this new open project.
+	    (ede-reset-all-buffers 1)
+
+	  ;; ELSE
+	  ;; There was no project, so switch to `ede-new' which is how
+	  ;; a user can select a new kind of project to create.
+	  (let ((default-directory (expand-file-name dir)))
+	    (call-interactively 'ede-new))))
+
+    ;; If the proposed directory isn't safe, then say so.
+    (error "%s is not an allowed project directory in `ede-project-directories'"
+	   dir)))
+
+(defun ede-check-project-directory (dir)
+  "Check if DIR should be in `ede-project-directories'.
+If it is not, try asking the user if it should be added; if so,
+add it and save `ede-project-directories' via Customize.
+Return nil iff DIR should not be in `ede-project-directories'."
+  (setq dir (directory-file-name (expand-file-name dir))) ; strip trailing /
+  (or (eq ede-project-directories t)
+      (and (functionp ede-project-directories)
+	   (funcall ede-project-directories dir))
+      ;; If `ede-project-directories' is a list, maybe add it.
+      (when (listp ede-project-directories)
+	(or (member dir ede-project-directories)
+	    (when (y-or-n-p (format "`%s' is not listed in `ede-project-directories'.
+Add it to the list of allowed project directories? "
+				    dir))
+	      (push dir ede-project-directories)
+	      ;; If possible, save `ede-project-directories'.
+	      (if (or custom-file user-init-file)
+		  (let ((coding-system-for-read nil))
+		    (customize-save-variable
+		     'ede-project-directories
+		     ede-project-directories)))
+	      t)))))
 
 (defun ede-new (type &optional name)
   "Create a new project starting of project type TYPE.
@@ -596,6 +710,11 @@
     (error "Cannot create project in non-existent directory %s" default-directory))
   (when (not (file-writable-p default-directory))
     (error "No write permissions for %s" default-directory))
+  (unless (ede-check-project-directory default-directory)
+    (error "%s is not an allowed project directory in `ede-project-directories'"
+	   default-directory))
+  ;; Make sure the project directory is loadable in the future.
+  (ede-check-project-directory default-directory)
   ;; Create the project
   (let* ((obj (object-assoc type 'name ede-project-class-files))
 	 (nobj (let ((f (oref obj file))
@@ -629,6 +748,10 @@
 	(ede-add-subproject pp nobj)
 	(ede-commit-project pp)))
     (ede-commit-project nobj))
+  ;; Once the project is created, load it again.  This used to happen
+  ;; lazily, but with project loading occurring less often and with
+  ;; security in mind, this is now the safe time to reload.
+  (ede-load-project-file default-directory)
   ;; Have the menu appear
   (setq ede-minor-mode t)
   ;; Allert the user
@@ -651,11 +774,16 @@
 (defun ede-rescan-toplevel ()
   "Rescan all project files."
   (interactive)
-  (let ((toppath (ede-toplevel-project default-directory))
-	(ede-deep-rescan t))
-    (project-rescan (ede-load-project-file toppath))
-    (ede-reset-all-buffers 1)
-    ))
+  (if (not (ede-directory-get-open-project default-directory))
+      ;; This directory isn't open.  Can't rescan.
+      (error "Attempt to rescan a project that isn't open")
+
+    ;; Continue
+    (let ((toppath (ede-toplevel-project default-directory))
+	  (ede-deep-rescan t))
+
+      (project-rescan (ede-load-project-file toppath))
+      (ede-reset-all-buffers 1))))
 
 (defun ede-new-target (&rest args)
   "Create a new target specific to this type of project file.
@@ -891,7 +1019,7 @@
   ;; Do the load
   ;;(message "EDE LOAD : %S" file)
   (let* ((file dir)
-	 (path (expand-file-name (file-name-directory file)))
+	 (path (file-name-as-directory (expand-file-name dir)))
 	 (pfc (ede-directory-project-p path))
 	 (toppath nil)
 	 (o nil))
@@ -920,13 +1048,11 @@
       ;; See if it's been loaded before
       (setq o (object-assoc (ede-dir-to-projectfile pfc toppath) 'file
 			    ede-projects))
-      (if (not o)
-	  ;; If not, get it now.
-	  (let ((ede-constructing pfc))
-	    (setq o (funcall (oref pfc load-type) toppath))
-	    (when (not o)
-	      (error "Project type error: :load-type failed to create a project"))
-	    (ede-add-project-to-global-list o)))
+
+      ;; If not open yet, load it.
+      (unless o
+	(let ((ede-constructing pfc))
+	  (setq o (ede-auto-load-project pfc toppath))))
 
       ;; Return the found root project.
       (when rootreturn (set rootreturn o))
@@ -980,13 +1106,7 @@
 	     (and root
 		  (ede-find-subproject-for-directory root updir))
 	     ;; Try the all structure based search.
-	     (ede-directory-get-open-project updir)
-	     ;; Load up the project file as a last resort.
-	     ;; Last resort since it uses file-truename, and other
-	     ;; slow features.
-	     (and (ede-directory-project-p updir)
-		  (ede-load-project-file
-		   (file-name-as-directory updir))))))))))
+	     (ede-directory-get-open-project updir))))))))
 
 (defun ede-current-project (&optional dir)
   "Return the current project file.
@@ -1000,11 +1120,7 @@
     ;; No current project.
     (when (not ans)
       (let* ((ldir (or dir default-directory)))
-	(setq ans (ede-directory-get-open-project ldir))
-	(or ans
-	    ;; No open project, if this dir pass project-p, then load.
-	    (when (ede-directory-project-p ldir)
-	      (setq ans (ede-load-project-file ldir))))))
+	(setq ans (ede-directory-get-open-project ldir))))
     ;; Return what we found.
     ans))
 
@@ -1059,12 +1175,13 @@
   "Return the project which is the parent of TARGET.
 It is recommended you track the project a different way as this function
 could become slow in time."
-  ;; @todo - use ede-object-project as a starting point.
-  (let ((ans nil) (projs ede-projects))
-    (while (and (not ans) projs)
-      (setq ans (ede-target-in-project-p (car projs) target)
-	    projs (cdr projs)))
-    ans))
+  (or ede-object-project
+      ;; If not cached, derive it from the current directory of the target.
+      (let ((ans nil) (projs ede-projects))
+	(while (and (not ans) projs)
+	  (setq ans (ede-target-in-project-p (car projs) target)
+		projs (cdr projs)))
+	ans)))
 
 (defmethod ede-find-target ((proj ede-project) buffer)
   "Fetch the target in PROJ belonging to BUFFER or nil."